How to handle pipelined requests with C#? - c#

I need to handle pipelined requests in my C# Mono (Mono version 3.12.1 on Linux) server, but I noticed that the second request in the pipeline is always ignored.
Here's my netcat command to test pipelining:
nc -v localhost 5000 < httppipe.txt
Here's the contents of httppipe.txt:
GET /heartbeat HTTP/1.1
Host: localhost
GET /heartbeat HTTP/1.1
Host: localhost
I'm pretty confident my netcat approach works because I tested it on a Java server successfully. (Meaning I saw 2 responses)
In my C# server, I've tried both GetResult and GetResultAsync. The code with GetResultAsync is basically pulled right from the MSDN example. It looks like this:
this.Listener = new HttpListener();
this.Listener.Prefixes.Add(uriPrefix);
this.Listener.BeginGetContext(new AsyncCallback(ListenerCallback),this.Listener);
public static void ListenerCallback(IAsyncResult result)
{
HttpListener listener = (HttpListener) result.AsyncState;
// Call EndGetContext to complete the asynchronous operation.
HttpListenerContext context = listener.EndGetContext(result);
listener.BeginGetContext(new AsyncCallback(ListenerCallback), listener);
HttpListenerRequest request = context.Request;
// Obtain a response object.
HttpListenerResponse response = context.Response;
// Construct a response.
string responseString = "<HTML><BODY> Hello world!</BODY></HTML>";
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
// Get a response stream and write the response to it.
response.ContentLength64 = buffer.Length;
System.IO.Stream output = response.OutputStream;
output.Write(buffer,0,buffer.Length);
// You must close the output stream.
output.Close();
}
EDIT: Also tried on Mono 4.0 on Linux to no avail.

Related

C#: Simple HTTP server

Based on MS example code I wrote a simple HTTP server.
listener = new HttpListener();
listener.Prefixes.Add("http://*:8000/");
while (true)
{
listener.Start();
HttpListenerContext context = listener.GetContext();
HttpListenerRequest request = context.Request;
// Obtain a response object.
HttpListenerResponse response = context.Response;
listener.Stop();
}
It seems that at each loop must start with "listenet.Start" and end with "listener.Stop"
When I used only "listener.Start" before the loop, I stopped getting messages from a working Angular client after 2 messages.
Can you please tell why ?
Thank you,
Zvika

Http Client sents request twice

I'm using an http client and an http server(listener)
In some cases when i'm having big delay-traffic in my network, http client sends request but it nevers takes a response from http listener.As a result i'm trying to resend my request to server. But, server already has took previous one request and re-runs the new one. In this case, server runs my request twice.
httplistener
HttpListener listener;
Thread t;
private void Form1_Load(object sender, EventArgs e)
{
t = new Thread(new ThreadStart(MyThreadMethod));
t.IsBackground = true;
listener = new HttpListener();
listener.Prefixes.Add("http://192.168.0.214:8282/");
listener.Start();
t.Start();
// label1.Text = "Opened";
}
void MyThreadMethod()
{
while (true)
{
IAsyncResult result = listener.BeginGetContext(new AsyncCallback(ListenerCallback), listener);
result.AsyncWaitHandle.WaitOne();
}
}
public void ListenerCallback(IAsyncResult result)
{
HttpListener listener = (HttpListener)result.AsyncState;
HttpListenerContext context = listener.EndGetContext(result);
string methodName = Convert.ToString(context.Request.Url);
//Here is my code
string Response = "OK";
HttpListenerResponse response = context.Response;
string responseString = Convert.ToString(Response);
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
response.ContentLength64 = buffer.Length;
System.IO.Stream output = response.OutputStream;
output.Write(buffer, 0, buffer.Length);
output.Close();
}
And here is my httpClient
HttpClient client = new HttpClient();
var content = new StringContent("my Data", Encoding.UTF8, "application/json");
var result = client.PostAsync("http://192.168.0.214:8282/", content).Result;
How can i prevent, when i'm having big delay_ms in my network, http listener running twice my requests?
This is a very common issue for distributed applications. You make a request, someone gets it, sometimes complete the request sometimes can not. Furthermore, sometimes you get the response and sometimes not. To deal with such situations, the rule of thumb is "your commands/requests should be idempotent". I mean, even if you send a request/command several times to a service, the result should not change and the command should only be executed only once. Accomplishing this sometimes can be very complicated but for simple scenarios you can simply accomplish this by adding a command id to your requests and a command log to your server. When server receives the request, it first checks whether this command already executed. If it was executed before, it returns the same success response, otherwise runs the command.

What sort of message does Snapd's API expect?

Snapd has documentation on a REST API.
I'm able to connect to the socket from C# using the following
var snapSocket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.IP);
var snapEndpoint = new UnixEndPoint("/run/snapd.socket");
snapSocket.Connect(snapEndpoint);
var req = Encoding.ASCII.GetBytes("GET /v2/system-info HTTP/1.1");
snapSocket.Send(req, req.Length, 0);
var bytesReceived = new byte[256];
var bytes = 0;
var response = "";
do
{
bytes = snapSocket.Receive(bytesReceived, bytesReceived.Length, 0);
response = response + Encoding.ASCII.GetString(bytesReceived, 0, bytes);
} while (bytes > 0);
Console.WriteLine(response);
But everything halts at snapSocket.Receive - a response is never sent. I suspect that there's something wrong with the message that I'm sending it.
It turns out that it expects a standard HTTP request, which means a Host: line, a Connection: Close line, and two \ns at the very end are required.
The documentation's following claim...
While it is expected to allow clients to connect using HTTPS over a TCP socket, at this point only a UNIX socket is supported.
... is meant only to imply that HTTPS and TCP do not work yet - HTTP is currently the valid request format even when using the UNIX Socket.
I am not fluent in C# at all, but maybe this python snippet can help lead into a solution:
import requests_unixsocket
session = requests_unixsocket.Session()
r = session.get('http+unix://%2Frun%2Fsnapd.socket/v2/snaps')
r.raise_for_status()
r.json()

How can I send GET request with sockets?

I want to send GET/POST request with sockets, and I have this code:
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Connect(Url, 80);
byte[] contentLenght = Encoding.ASCII.GetBytes(Data);
string[] masRequestString ={
"GET /"+Data+" HTTP/1.1\r\n" ,
"Host: "+Url+"\r\n",
"User-Agent: "+textBox1.Text+"\r\n",
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n",
"Content-Type: application/x-www-form-urlencoded; charset=UTF-8\r\n",
"Referer: "+textBox2.Text+"\r\n"};
string request = string.Concat(masRequestString);
Byte[] bytesSent = Encoding.ASCII.GetBytes(request);
socket.Send(bytesSent, bytesSent.Length, 0);
Byte[] bytesReceived = new Byte[0x400];
int bytes = socket.Receive(bytesReceived, bytesReceived.Length, 0);
string content = Encoding.ASCII.GetString(bytesReceived, 0, bytes);
When I try to send the request sniffers don't see it. Why?
I have no idea of how you do sniffing but
You are at not sending a correct request, because it misses an \r\n at the end.
You are expecting the server to close the connection after the response is done. Instead you need to care about content-length header and chunked encoding.
And apart from that sending a Content-type header with a GET request makes no sense, because there will be no content sent inside a GET request (only in the response).
I suggest you first get a deeper knowledge of HTTP works before you are trying to implement it. Much better of course would be to use already existing implementations, because HTTP is not the simply protocol it seems to be after having only a short look.
Don't try implement HTTP yourself, socket can take your data and wrap it.
Use HTTP library:
using System.Net;
string url = "https://www.example.com/scriptname.php?var1=hello";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Stream resStream = response.GetResponseStream();

How can I avoid a "Bad Request - Invalid Hostname" error when making a REST call from a Compact Framework client?

I used this code from here to try to call a REST Controller method on a Web API server app from a Compact Framework client:
public static void SendXMLFile3(string uri, string data)
{
WebRequest request = WebRequest.Create (uri);
request.Method = "POST";
string postData = data;
byte[] byteArray = Encoding.UTF8.GetBytes (postData);
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = byteArray.Length;
Stream dataStream = request.GetRequestStream ();
dataStream.Write (byteArray, 0, byteArray.Length);
dataStream.Close ();
WebResponse response = request.GetResponse ();
MessageBox.Show(((HttpWebResponse) response).StatusDescription);
dataStream = response.GetResponseStream ();
StreamReader reader = new StreamReader (dataStream);
string responseFromServer = reader.ReadToEnd();
MessageBox.Show(responseFromServer);
reader.Close ();
dataStream.Close ();
response.Close ();
}
...I had earlier tried this code, which I got from the book "Microsoft .NET Compact Framework":
public static void SendXMLFile2(string uri, string data)
{
WebRequest req = WebRequest.Create(uri);
req.Method = "Post";
req.ContentType = "text/plain; charset=utf-8";
byte[] encodedBytes = Encoding.UTF8.GetBytes(data);
req.ContentLength = encodedBytes.Length;
Stream requestStream = req.GetRequestStream();
requestStream.Write(encodedBytes, 0, encodedBytes.Length);
requestStream.Close();
WebResponse result = req.GetResponse();
MessageBox.Show(result.ToString());
}
...but I get "400 - Bad Request" with the new (as well as the old) code.
My initial attempt also does not work, with the same result (400):
public static string SendXMLFile(string xmlFilepath, string uri, int timeout)
{
HttpWebRequest myHttpWebRequest=(HttpWebRequest)WebRequest.Create(uri);
myHttpWebRequest.AllowWriteStreamBuffering=false;
string postData = "<Command><DSD><line_id>1</line_id><invoice_no>david_dsd</invoice_no>. . .</DSD></Command>"; // TODO: if this works, replace it with the real data
myHttpWebRequest.Method="POST";
UTF8Encoding encodedData = new UTF8Encoding();
byte[] byteArray=encodedData.GetBytes(postData);
myHttpWebRequest.ContentType = "application/xml";
myHttpWebRequest.ContentLength=byteArray.Length;
Stream newStream=myHttpWebRequest.GetRequestStream();
newStream.Write(byteArray,0,byteArray.Length);
newStream.Close();
HttpWebResponse myHttpWebResponse=(HttpWebResponse)myHttpWebRequest.GetResponse();
return myHttpWebResponse.StatusDescription;
}
There is much more about the plethora of variations I have tried here, where I have reached my length-of-post limit.
UPDATE
Note that the Server code doesn't know/care that the file is XML:
[HttpPost]
[Route("api/inventory/sendXML/{userId}/{pwd}/{filename}")]
public async Task SendInventoryXML(String userId, String pwd, String fileName)
{
Task task = Request.Content.ReadAsStreamAsync().ContinueWith(t =>
{
var stream = t.Result;
using (FileStream fileStream = File.Create(String.Format(#"C:\HDP\{0}.xml", fileName), (int)stream.Length))
{
byte[] bytesInStream = new byte[stream.Length];
stream.Read(bytesInStream, 0, (int)bytesInStream.Length);
fileStream.Write(bytesInStream, 0, bytesInStream.Length);
}
});
}
UPDATE 2
I tried Charles to see if it would pick up the local HTTP traffic, but it is also deaf to such (like Fiddler, without special ministrations, anyway). This is what Charles looks like after getting the "400 - Bad Request" error:
UPDATE 3
I found this suggestion somewhere to get Fiddler to show local HTTP traffic:
Tools--> Fiddler Options. Choose Connections tab. Check the 'USe PAC Script' option.
...but it didn't work - I still see no HTTP traffic when getting the "400 (Bad Request)" message.
UPDATE 4
I am now seeing "400 (Bad Request)" in Fiddler 2, too; to get it, I enter any of the following in Postman (don't see this in Fiddler when calling from CE/CF/handheld app):
http://SHANNON2:21608/api/inventory/sendXML/su/su/blablee // Hostname
http://SHANNON2.:21608/api/inventory/sendXML/su/su/blablee // Hostname with Fiddler-fooler appended "."
http://192.168.125.50:21608/api/inventory/sendXML/su/su/blablee // IP Address
(Fiddler does not capture anything if I replace the hostname or IP Address with "localhost")
Note: For these URLs in Postman, I have "Post" (as opposed to GET, etc.) selected, and an XML file attached.
Inspectors.Headers in Fiddler shows:
POST /api/inventory/sendXML/su/su/blablee HTTP/1.1
Although I consider this a minor debugging victory, I still don't see why I'm getting the "400" error.
Fiddler tells me, in the Inspectors.WebView pane:
Bad Request - Invalid Hostname
--------------------------------------------------------------------------------
HTTP Error 400. The request hostname is invalid.
How can that be? When I run it from Postman, I hit the breakpoint in the server - if the hostname is invalid, why is it being reached?
UPDATE 5
Testing the call from Fiddler Composer and Postman, the only way I can reach the breakpoint in the server code is by using "localhost" - replacing that with the PC's IPAddress (192.168.125.50) or HostName (SHANNON2) does not reach the breakpoint. While "interesting," calling "localhost" from the handheld device is obviously not an option.
UPDATE 6
Related new question here.
UPDATE 7
The crux of the biscuit was adding at the command prompt either this:
netsh http add urlacl url=http://shannon2:80/ user=everyone
...or this:
netsh http add urlacl url=http://shannon2:8080/ user=everyone
See Update 5 here for more details
Adding my observations, if you are facing the issue Bad Request - Invalid Hostname while calling the API from Postman. Something like this.
Consider adding the Host in Headers, it will work.
Updated after someone is not able to view the image: If the images are not loading, then basically this you have to do:
Basically, you have to add Content-Length and Host. Postman auto calculates when the request is sent. These can be enabled in postman by unhiding auto-generated headers in Postman. or you can set by your own.
The crux of the biscuit was adding at the command prompt either this:
netsh http add urlacl url=http://shannon2:80/ user=everyone
...or this:
netsh http add urlacl url=http://shannon2:8080/ user=everyone
See Update 5 here for more details

Categories

Resources