.Net HttpListener.GetContext() gives "503 Service Unavailable" to the client
Asked Answered
E

2

7

I am trying to write a very very simple custom Http server in C#.

The code to achieve a connection is as simple as .Net offers it:

 // get our IPv4 address
 IPAddress[] localIPs = Dns.GetHostAddresses(Dns.GetHostName());
 IPAddress theAddress = localIPs.Where(ip => ip.AddressFamily == AddressFamily.InterNetwork).FirstOrDefault();

 // Create a http listener.
 var listener = new HttpListener();
 string myUrlPrefix = $"http://{theAddress.ToString()}:{port}/MyService/";
 listener.Prefixes.Add(myUrlPrefix);
 Console.WriteLine($"Trying to listen on {myUrlPrefix}");
 listener.Start();
 Console.WriteLine("Listening...");
 HttpListenerContext context = null;
 try
 {
     context = listener.GetContext();
 }
 catch(Exception ex)
 {
     Console.WriteLine(ex.Message);
 }

 HttpListenerRequest request = context.Request;

When running this, GetContext() blocks, never returns and does not throw an exception.

listener.Start() works -- the URL is properly reserved for the correct user with 'netsh http add urlacl...'., else Start() would throw. The URL is also only reserved once.

I can see with netstat -na that the port is being listened on.

I am trying to access this with either a browser or with the cygwin implementation of wget. Both give me "ERROR 503: Service Unavailable.".

Here is the wget output:

$ wget http://127.0.0.1:45987/MyService/
--2016-03-06 14:54:37--  http://127.0.0.1:45987/MyService/
Connecting to 127.0.0.1:45987... connected.
HTTP request sent, awaiting response... 503 Service Unavailable
2016-03-06 14:54:37 ERROR 503: Service Unavailable.

This means that the TCP connection gets established, yet HttpListener.GetContext() does not bother to service the request and give me the context, it also doesn't bother to at least throw an exception to tell me what's wrong. It just closes the connection. From the debug output, it doesn't even have a First-Chance exception inside that it would catch itself.

I have searched the Net and Stackoverflow up and down and don't find anything useful.

There are no errors in the Windows Event log.

This is Windows 10 Pro and Microsoft Visual Studio Community 2015 Version 14.0.23107.0 D14REL with Microsoft .NET Framework Version 4.6.01038.

It may be worth mentioning that I have also tested this with all firewalls off, still the same result.

Does anyone have any idea what could be going wrong in the GetContext() and how I could debug or solve this?

======

EDIT: I have enabled .Net Framework source stepping and have stepped into GetContext(). The blocking and refusal to service http requests happens in something called "UnsafeNclNativeMethods.HttpApi.HttpReceiveHttpRequest" which, I guess, might be the native HttpReceiveHttpRequest method documented at https://msdn.microsoft.com/en-us/library/windows/desktop/aa364495(v=vs.85).aspx . So it's the native API that is refusing my request.

Ezekielezell answered 6/3, 2016 at 4:7 Comment(9)
I tried your example and it works for me but I have Win7.Steady
Have you tried this: #26413102?Bold
Thanks, csharpfolk, yes I have. That's what I meant when I wrote "I have searched ... Stackoverflow up and down and don't find anything useful" and that's also why I mentioned that the URL reservation is correct and not doubled up. I have played around with several prefixes.Ezekielezell
@JS, could you please more detailed information about error? Internal server stack trace, check InnerException property if available. Need more information. Also, did you try to run under local IIS?Watford
Thanks, Anton Norko: As I said in my post, there is no exception thrown. So, no stacktrace, no inner exception. And not under IIS, that's what I wanted to say when I said 'as simple as .Net offers it'. That means the context is a simple out-of-the-box console application which contains pretty much no other code. I wish I could give more detailed information.Ezekielezell
Use a sniffer like wireshark or fiddler to trace internet layer. Also try with a Webbrowser and compare to vs results. Usually it is a cookie or certificate. Sometimes deleting cookies from IE history solves issue.Longhair
Hi @jdweng, neither fiddler nor wireshark give me anything (so far everything happens on localhost), and the Webbrowsers I have tried get the same result as wget. I used wget because it at least confirms that the basic TCP connection works.Ezekielezell
The 503 error must be shown in the fiddler results. 503 is not a local host error. It is returned from the server.Longhair
@JS Did you try to change .Net framework version? Let say .net 4.5... Could you check it please?Watford
T
3

I just had the same problem, and it was caused by a URL ACL.

From an elevated command prompt

netsh http show urlacl will show all URL ACLs

netsh http delete urlacl http://+:1234/ will delete the URL ACL with the URL http://+:1234/

netsh http add urlacl url=http://+:1234/ user=Everyone will add a URL ACL that any local user can listen on (normally this should use the network service account)

This URL needs to match the prefix in your HttpListener.

If you're only listening on localhost, you don't need a URL ACL.

Normally not having the correct the URL ACL results in a permissions error when starting the listener. I'm not sure what the specific situation is that causes it to get past this but then return 503 Service Unavailable on GetContext.

Tobolsk answered 8/8, 2019 at 7:59 Comment(0)
E
0

I haven't found a solution, but a workaround.

Interestingly, if I go a level deeper and use a plain TcpListener, I have no problem receiving the request as plain text.

 // Create a Tcp listener.
 mTcpListener = new TcpListener(theAddress, port);

 Console.WriteLine($"Trying to listen on {theAddress}:{port}");
 mTcpListener.Start();
 Console.WriteLine("Listening...");
 Socket socket = null;
 try
 {
     socket = mTcpListener.AcceptSocket();
 }
 catch (Exception ex)
 {
     Console.WriteLine(ex.Message);
     return;
 }
 // wait a little for the socket buffer to fill up
 await Task.Delay(20);

 int bytesAvailable = socket.Available;
 var completeBuffer = new List<byte>();
 while (bytesAvailable > 0)
 {
     byte[] buffer = new byte[bytesAvailable];
     int bytesRead = socket.Receive(buffer);
     completeBuffer.AddRange(buffer.Take(bytesRead));

     bytesAvailable = socket.Available;
 }

 byte[] receivedBytes = completeBuffer.ToArray();

 string receivedString = Encoding.ASCII.GetString(receivedBytes);

... works like a charm. The returned string is

GET /MyService/ HTTP/1.1
User-Agent: Wget/1.17.1 (cygwin)
Accept: */*
Accept-Encoding: identity
Host: 127.0.0.1:45987
Connection: Keep-Alive

So, as a workaround, I could implement my own http request parser and response generator... not that I like being forced to reinvent the wheel.

Ezekielezell answered 6/3, 2016 at 13:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.