Press "Enter" to skip to content

2 – Un sistema di messaggistica per molteplici usi – HTTP Sender e HTTP Listener – Il progetto di test

Nel post precedente abbiamo creato una libreria generica contenente le classi che implementano un HTTP listener ed un HTTP sender, in questo realizziamo una applicazione di test WPF che ci permette di verificare il corretto funzionamento delle due classi, in seguito proveremo ad inserire questi due oggetti in una applicazione reale, o meglio in due applicazioni, il servizio windows che abbiamo sviluppato e la sua console.

Il nostro progetto di test è una semplice applicazione WPF in cui abbiamo inserito 3 finestre, una finestra principale, una finestra per il Sender ed una per il Listener, vediamo come è costruita la struttura dell’applicazione:

http_02_solution_01

Vediamo in dettaglio l’interfaccia ed il codice.

http_02_main_window

La finestra principale ha un interfaccia molto semplice, solo 2 Button, per far partire il sender ed il listener. Non ne discuteremo neppure il codice in quanto è banale.

La classe ReceiveWindow

http_02_TestListener_01

L’interfaccia è molto semplice anche in questo caso, vediamo quindi come utilizziamo la classe costruita nella libreria base:

public partial class ReceiveWindow : Window,INotifyPropertyChanged
{

	ListenerHTTP mListener;

	public ReceiveWindow()
	{
		InitializeComponent();
		this.Icon = BitmapFrame.Create(new Uri("pack://application:,,,/dnwIco.ico", UriKind.RelativeOrAbsolute));
		txtAddress.Text = "*"; // "127.0.0.1";
		txtPort.Text = "8080";
		ResponseToSend = "Nice message received thank you";
		this.DataContext = this;
		ShowStatus();
	}

La classe della finestra, inizializziamo i valori di default per la connessione del listener, quindi ascolta qualsiasi indirizzo locale (nel caso normale 127.0.0.1) sulla porta 8080. Nella discussione abbiamo trascurato volutamente le property per il binding ai controlli della form e i click dei button che cancellano la finestra result e rileggono lo status del listener.

private void ShowStatus()
{
	try
	{
		string s = "Listner not created";
		if (mListener != null)
		{
			if (mListener.IsListening)
			{
				s = "Listener is in listen mode";
			}
			else
			{
				s = "Listener non listening";
			}
		}
		txtStatus.Text = string.Format(FMT_STATUS, s);
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message, "More info in the log", MessageBoxButton.OK, MessageBoxImage.Error);
		EventLogger.SendMsg(ex);
	}
}

Un metodo accessorio per indicare lo stato del listener dopo ogni operazione.

private void btnCreate_Click(object sender, RoutedEventArgs e)
{
	try
	{
		if (CreateListener(true))
		{
			LOG("Listener created successfully");
		}
		else
		{
			LOG("Listener not created");
		}
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message, "More info in the log", MessageBoxButton.OK, MessageBoxImage.Error);
		EventLogger.SendMsg(ex);
	}
	finally
	{
		ShowStatus();
	}
}

Il click del button di creazione molto semplice, prova a creare il listener ed in caso di successo o di errore scrive una riga sulla textbox result.

private void LOG(params string[] lines)
{
	try
	{
		StringBuilder sb = new StringBuilder();
		foreach (string item in lines)
		{
			sb.AppendLine(item);
		}
		sb.AppendLine();

		if (txtResult.Dispatcher.Thread == Thread.CurrentThread)
		{
			txtResult.Text = txtResult.Text.Insert(0, sb.ToString());
		}
		else
		{
			txtResult.Dispatcher.BeginInvoke((Action)delegate { LOG(lines); }, null);
		}
	}
	catch (Exception ex)
	{
	}
}

Il metodo di visualizzazione dei messaggi di result delle varie operazioni, è creato in modo tale da verificare quale thread lo ha invocato in modo da effettuare un Dispatch al thread principale della UI quando il thread chiamante non fosse quello principale.

private bool CreateListener(bool closeIfExists)
{
	bool ok = false;
	try
	{
		if ((closeIfExists) && (mListener != null))
		{
			mListener.Stop();
			mListener = null;
		}

		HttpConnectionInfo c = CreateConnection();
		if (c == null)
		{
			LOG("Address or port invalid!", "Listener not created");
		}
		else
		{
			mListener = new ListenerHTTP(c, System.Threading.ThreadPriority.Normal, autoStart: false);
			mListener.RequestReceived += SomethingRecived;
		}
		ok = (mListener != null);
	}
	catch (Exception ex)
	{
		LOG(ex.Message);
		ok = false;
		if (mListener != null)
		{
			mListener.Stop();
			mListener = null;
		}
	}
	finally
	{
		ShowStatus();
	}
	return (ok);
}

Il metodo che crea il listener, vediamo che cosa fa:

  • Se gli è stato indicato di rigenerare il listener se esiste già , lo ferma, e lo elimina.
  • Crea la stringa di connessione e verifica che sia valida
  • Genera il listener e gli collega l’event handler che elabora i messaggi ricevuti.
  • Ritorna true se tutto è andato bene e mostra lo status del listener.

private HttpConnectionInfo CreateConnection()
{
	HttpConnectionInfo c = new HttpConnectionInfo();
	c.Address = txtAddress.Text;
	if (!txtPort.Text.XDwIsNullOrTrimEmpty())
	{
		int port = 0;
		if (int.TryParse(txtPort.Text, out port))
		{
			c.Port = port;
		}
	}
	return (c);
}

Il metodo che crea la stringa di connessione per il servizio in base a quanto da noi impostato sulla UI.

private void btnStart_Click(object sender, RoutedEventArgs e)
{
	try
	{
		if (mListener == null)
		{
			LOG("Listener non started");
		}
		else
		{
			if (!mListener.IsListening)
			{
				mListener.Start(System.Threading.ThreadPriority.Normal, isBackgroundThread: true);
				LOG("Started");
			}
			else
			{
				LOG("Already started.....");
			}
		}
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message, "More info in the log", MessageBoxButton.OK, MessageBoxImage.Error);
		EventLogger.SendMsg(ex);
	}
	finally
	{
		ShowStatus();
	}
}

Il bottone di Start, che avvia l’ascolto sull’indirizzo e sulla posta configurata.

  • Controlla che il listener sia stato creato e se non lo fosse da un errore
  • Se il listener sta già ascoltando avvisa e non fa nulla
  • Altrimenti fa partire il ciclo di ascolto, in questo caso come thread a bassa priorità , visto che siamo in una applicazione con UI.

private void btnStop_Click(object sender, RoutedEventArgs e)
{
	try
	{
		if (mListener == null)
		{
			LOG("Listener non started");
		}
		else
		{
			if (mListener.IsListening)
			{
				mListener.Stop();
				LOG("Listener stopped");
			}
			else
			{
				LOG("Already stopped.....");
			}
		}
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message, "More info in the log", MessageBoxButton.OK, MessageBoxImage.Error);
		EventLogger.SendMsg(ex);
	}
	finally
	{
		ShowStatus();
	}
}

Il bottone stop del listener:

  • Controlla se il listener esiste
  • Se esiste e non è in ascolto avvisa
  • Altrimenti lo ferma.

private string SomethingRecived(HTTPDataReceivedEventArgs e)
{
	string result = "KO";
	StringBuilder sb = new StringBuilder();
	try
	{
		sb.AppendLine("-----------------------------------");
		sb.AppendLine(string.Format("Requested URL:        {0}", e.RequestedURL));
		string txD = "Unknown!!!!";
		if (e.TxDate.HasValue)
		{
			txD = e.TxDate.Value.ToUniversalTime().ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'");
		}
		sb.AppendLine(string.Format("UTC Tx DateTime:      {0}", txD));
		sb.AppendLine(string.Format("Sender EndPoint:      {0}", e.SenderEndPoint != null ? e.SenderEndPoint.ToString() : "'null'"));
		sb.AppendLine(string.Format("Destination EndPoint: {0}", e.DestinationEndPoint != null ? e.DestinationEndPoint.ToString() : "'null'"));
		sb.AppendLine(string.Format("Http Method:          {0}", e.HttpMethod));
		if (e.DataReceived != null)
		{
			sb.AppendLine("-------- Start data recived -------");
			sb.AppendLine(e.DataReceived);
			sb.AppendLine("-------- End data recived ---------");
		}
		if ((e.FieldsCollection != null) && (e.FieldsCollection.Count > 0))
		{
			sb.AppendLine("-------- Start Collection -------");
			for (int i = 0; i < e.FieldsCollection.Count; i++)
			{
				string[] lines = e.FieldsCollection.GetValues(i);
				sb.AppendLine(string.Format("{0}: {1} ({2} lines) Starts Below -----------", i, e.FieldsCollection.Keys[i], lines.Length));
				for (int cntVals = 0; cntVals < lines.Length; cntVals++)
				{
					sb.AppendLine(lines[cntVals]);
				}
				sb.AppendLine(string.Format("{0}: {1} ({2} lines) Ends Above -------------", i, e.FieldsCollection.Keys[i], lines.Length));
			}
			sb.AppendLine("-------- End Collection ---------");
		}
		result = ResponseToSend;

		sb.AppendLine(string.Format("------------ Response sended: {0}", result));
		sb.AppendLine("-----------------------------------");
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message, "More info in the log", MessageBoxButton.OK, MessageBoxImage.Error);
		EventLogger.SendMsg(ex);
	}

	LOG(sb.ToString());

	return (result);
}

Il metodo che formatta e visualizza quello che viene ricevuto dal listener visualizzandolo sulla textbox result.

La classe SendWindow

http_02_TestSender_01

Anche questa finestra è piuttosto semplice, come la precedente, abbiamo i dati per la trasmissione, quindi indirizzo e porta ove trasmettere, tre campi, per simulare 3 property Name Value da inviare al listener e la finestra result per visualizzare il risultato della trasmissione quando viene effettuata.

public partial class SendWindow : Window, INotifyPropertyChanged
{
	public SendWindow()
	{
		InitializeComponent();
		this.DataContext = this;
		Address = "127.0.0.1";
		Port = "8080";
		this.Icon = BitmapFrame.Create(new Uri("pack://application:,,,/dnwIco.ico", UriKind.RelativeOrAbsolute));
	}

Implementiamo la classe in modo che possa essere il model di se stessa quindi aggiungeremo l’evento PropertyChanged al suo interno e mapperemo  una serie di property per il  Binding dei controlli textbox inseriti sulla window che non discuteremo in quanto ne abbiamo parlato nei primi post dedicati al binding WPF se siete interessati alla UI e al Binding sui controlli, c’è la serie di post dedicata allo User Control per le stringhe di connessione a sql server che iniziano QUI che spiegano quello che riguarda il binding della UI.

private void btnSendGet_Click(object sender, RoutedEventArgs e)
{
	try
	{
		HttpConnectionInfo conn = CreateConnection();
		NameValueCollection dataToSend = CreateDataToSend();
		if (conn != null)
		{
			string result = SenderHTTP.HTTPGet(conn, dataToSend);
			LOG(result);
		}
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message, "More info in the log", MessageBoxButton.OK, MessageBoxImage.Error);
		EventLogger.SendMsg(ex);
	}
}

Il send usando il metodo GET, che crea la connessione al listener, compone i dati da inviare ed utilizza l’helper da noi creato per spedire il messaggio.

private HttpConnectionInfo CreateConnection()
{
	HttpConnectionInfo c = new HttpConnectionInfo();
	c.Address = Address;
	if (!Port.XDwIsNullOrTrimEmpty())
	{
		int port = 0;
		if (int.TryParse(Port, out port))
		{
			c.Port = port;
		}
	}
	if (c == null)
	{
		LOG("Address or port invalid!", "Sender not created");
	}
	return (c);
}

private NameValueCollection CreateDataToSend()
{
	NameValueCollection values = new NameValueCollection();

	values.Add(Key1, Content1);
	values.Add(Key2, Content2);
	values.Add(Key3, Content3);

	return (values);
}

I due metodi helper per generare la connection e la Name Value collection dei dati da spedire.

private void btnSendPOST_Click(object sender, RoutedEventArgs e)
{
	try
	{
		HttpConnectionInfo conn = CreateConnection();
		NameValueCollection dataToSend = CreateDataToSend();
		if (conn != null)
		{
			string result = SenderHTTP.HTTPPost(conn, dataToSend, false);
			LOG(result);
		}
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message, "More info in the log", MessageBoxButton.OK, MessageBoxImage.Error);
		EventLogger.SendMsg(ex);
	}
}

L’invio dei dati tramite POST senza attendere un risultato da parte del listener.

private void btnSendPOSTResponse_Click(object sender, RoutedEventArgs e)
{
	try
	{
		HttpConnectionInfo conn = CreateConnection();
		NameValueCollection dataToSend = CreateDataToSend();
		if (conn != null)
		{
			string result = SenderHTTP.HTTPPost(conn, dataToSend, true);
			LOG(result);
		}
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message, "More info in the log", MessageBoxButton.OK, MessageBoxImage.Error);
		EventLogger.SendMsg(ex);
	}
}

L’invio di dati tramite POST con l’attesa del risultato della trasmissione.

Conclusioni

In questi due post dedicati ai messaggi HTTP abbiamo visto come implementare due classi per spedire e ricevere messaggi HTTP, abbiamo visto come utilizzarle in una semplice applicazione WPF che ne testa il funzionamento.

Il progetto di esempio relativo a questo articolo è disponibile al link seguente:

Il progetto delle librerie comuni in cui sono le classi helper relative alla gestione dell’HTTP sono disponibili al link seguente:

Per qualsiasi domanda, curiosità , approfondimento, potete usare il link alla form di contatto in cima alla pagina.