Download Source - SimpleHttpServer-2013-03-23.zip - 9.2 KB
Download Source from Github
jeske/SimpleHttpServer
Introduction
This article covers a simple HTTP server class which you may incorporate into your own projects, or review to learn more about the HTTP protocol.
Background
High performance web services are often hosted in rock solid webservices like IIS, Apache, or Tomcat. However, HTML is such a flexible UI language, that it can be useful to serve an HTML UI out of practically any application or backend server. In these situations, the overhead and configuration complexity of an external webserver is seldom worth the trouble. What's needed is a simple HTTP class which can be easily embedded to service simple web requests. This class meets that need.
Using the Code
First let's review how to use the class, and then we'll dig into some of the details of how it operates. We begin by subclassing
HttpServer
and providing implementations for the two
abstract
methods
handleGETRequest
and
handlePOSTRequest
...
public
class
MyHttpServer : HttpServer {
public
MyHttpServer(
int
port)
:
base
(port) {
public
override
void
handleGETRequest(HttpProcessor p) {
Console.WriteLine(
"
request: {0}"
, p.http_url);
p.writeSuccess();
p.outputStream.WriteLine(
"
<html><body><h1>test server</h1>"
);
p.outputStream.WriteLine(
"
Current Time: "
+ DateTime.Now.ToString());
p.outputStream.WriteLine(
"
url : {0}"
, p.http_url);
p.outputStream.WriteLine(
"
<form method=post action=/form>"
);
p.outputStream.WriteLine(
"
<input type=text name=foo value=foovalue>"
);
p.outputStream.WriteLine(
"
<input type=submit name=bar value=barvalue>"
);
p.outputStream.WriteLine(
"
</form>"
);
public
override
void
handlePOSTRequest(HttpProcessor p, StreamReader inputData) {
Console.WriteLine(
"
POST request: {0}"
, p.http_url);
string
data = inputData.ReadToEnd();
p.outputStream.WriteLine(
"
<html><body><h1>test server</h1>"
);
p.outputStream.WriteLine(
"
<a href=/test>return</a><p>"
);
p.outputStream.WriteLine(
"
postbody: <pre>{0}</pre>"
, data);
Once a simple request processor is provided, one must instantiate the server on a port, and start a thread for the main server listener.
HttpServer httpServer =
new
MyHttpServer(
8080
);
Thread thread =
new
Thread(
new
ThreadStart(httpServer.listen));
thread.Start();
If you compile and run the sample project, you should be able to point a web-browser of choice at http://localhost:8080 to see the above simple HTML pages rendered. Let's take a brief look at what's going on under the hood.
This simple webserver is broken into two components. The
HttpServer
class opens a
TcpListener
on the incoming port, and sits in a loop handling incoming TCP connect requests using
AcceptTcpClient
(). This is the first step of handling an incoming TCP connection. The incoming request arrived on our "well known port", and this accept process creates a fresh port-pair for server to communicate with this client on. That fresh port-pair is our
TcpClient
session. This keeps our main accept port free to accept new connections. As you can see in the code below, each time the listener returns a new
TcpClient
,
HttpServer
creates a new
HttpProcessor
and starts a new thread for it to operate in. This class also contains the
abstract
methods our subclass must implement in order to produce a response.
public
abstract
class
HttpServer {
protected
int
port;
TcpListener listener;
bool
is_active =
true
;
public
HttpServer(
int
port) {
this
.port = port;
public
void
listen() {
listener =
new
TcpListener(port);
listener.Start();
while
(is_active) {
TcpClient s = listener.AcceptTcpClient();
HttpProcessor processor =
new
HttpProcessor(s,
this
);
Thread thread =
new
Thread(
new
ThreadStart(processor.process));
thread.Start();
Thread.Sleep(
1
);
public
abstract
void
handleGETRequest(HttpProcessor p);
public
abstract
void
handlePOSTRequest(HttpProcessor p, StreamReader inputData);
At this point, the new client-server TCP connection is handed off to the
HttpProcessor
in its own thread. The
HttpProcessor
's job is to properly parse the HTTP headers, and hand control to the proper
abstract
method handler implementation. Let's look at just a few small parts of the HTTP header processing. The first line of an HTTP Request resembles the following:
GET /myurl HTTP/1.0
After setting up the input and output stream in
process()
, our
HttpProcessor
calls
parseRequest()
, where the above HTTP request line is received and parsed.
public
void
parseRequest() {
String
request = inputStream.ReadLine();
string[] tokens = request.Split(
'
'
);
if
(tokens.Length !=
3
) {
throw
new
Exception(
"
invalid http request line"
);
http_method = tokens[0].ToUpper();
http_url = tokens[1];
http_protocol_versionstring = tokens[2];
Console.WriteLine(
"
starting: "
+ request);
The HTTP request line is always three parts, so we simply use a
string.Split()
call to separate it into three pieces. The next step is to receive and parse the HTTP headers from the client. Each header-line includes a type of the form
KEY:Value
. An empty line signifies the end of the HTTP headers. Our code to
readHeaders
is the following:
public
void
readHeaders() {
Console.WriteLine(
"
readHeaders()"
);
String
line;
while
((line = inputStream.ReadLine()) !=
null
) {
if
(line.Equals(
"
"
)) {
Console.WriteLine(
"
got headers"
);
return
;
int
separator = line.IndexOf(
'
:'
);
if
(separator == -1) {
throw
new
Exception(
"
invalid http header line: "
+ line);
String
name = line.Substring(
0
, separator);
int
pos = separator +
1
;
while
((pos < line.Length) && (line[pos] ==
'
'
)) {
pos++;
string
value
= line.Substring(pos, line.Length - pos);
Console.WriteLine(
"
header: {0}:{1}"
,name,
value
);
httpHeaders[name] =
value
;
For each line, we look for the colon (
separator, grabbing the
string
before as a name, and the
string
after as a value. When we reach an empty header-line, we return because we have received all headers.
At this point, we know enough to handle our simple
GET
or
POST
, so we dispatch to the proper handler. In the case of a post, there is some trickiness to deal with in accepting the post data. One of the request headers includes the
content-length
of the post data. While we wish to let our subclass's
handlePOSTRequest
actually deal with the post data, we need to only allow them to request content-length bytes off the
stream
, otherwise they will be stuck blocking on the input stream waiting for data which will never arrive. In this simple server, we handle this situation with the dirty but effective strategy of reading all the post data into a
MemoryStream
before sending this data to the
POST
handler. This is not ideal for a number of reasons. First, the post data may be large. In fact it may be a file upload, in which case buffering it into memory may not be efficient or even possible. Ideally, we would create some type of stream-imitator that could be setup to limit itself to
content-length
bytes, but otherwise act as a normal stream. This would allow the
POST
handler to pull data directly off the stream without the overhead of buffering in memory. However, this is also much more code. In many embedded HTTP servers, post requests are not necessary at all, so we avoid this situation by simply limiting
POST
input data to no more than 10MB.
Another simplification of this simple server is the
content-type
of the return data. In the HTTP protocol, the server always sends the browser the
MIME-Type
of the data which it should be expecting. In
writeSuccess()
, you can see that this server always indicates a
content-type
of
text/html
. If you wish to return other content types, you will need to extend this method to allow your handler to supply a content type response before it sends data to the client.
Points of Interest
This
SimpleHttpServer
only implements a very bare-bones subset of even the basic HTTP/1.0 spec. Further revisions of the HTTP specification have included more complex and very valuable improvements, including compression, session keep alive, chunked responses, and lots more. However, because of the excellent and simple design of HTTP, you'll find that even this very bare-bones code is capable of serving pages which are compatible with modern web-browsers.
Other similar embeddable servers include:
Sample HTTP Server Skeleton in C#
Embedded .NET HTTP Server
System.Net HttpListener
(as of .NET Framework 3.5 SP1)
The HTTP server provided in
zeroconf.codeplex.com
History
December 19, 2010: Initial version posted
December 22, 2010: Removed
StreamReader
from input side so we can properly get raw
POST
data
March 2, 2012: corrected line-terminators to use WriteLine() for \r\n instead of just \n
David Jeske is an Entrepreneur and Computer Programmer, currently living in San Francisco, California.
He earned his B.S. Computer Engineering at University of Illnois at Champaign/Urbana (UIUC), and has worked at several Silicon Valley companies, including Google, Yahoo, eGroups.com, 3dfx, and Akklaim Entertainment. He has managed and architected extremely high-traffic websites, including Yahoo Groups and orkut.com, and has experience in a broad spectrum of technology areas including scalability, databases, drivers, system software, and 3d graphics.
You can contact him at davidj -a-t- gmail (dot) com for personal messages about this article.
i am new to web programming and find difficulty in accessing webserver after compiling , basically what is the url to access this test server after running
Sign In
·
View Thread
First off thanks for the super handy bit of code!
I'm modifying another project that uses your simple webserver as a backend to host a webpage that has javascript on it. The webpage will display properly on all other browsers except for Safari Mobile (iPhone).
First I tried a simple test by making 2 test webpages with javascript on them to see if it was my webpage causing issues (a page with an alert box, and a page with the date/time on it). Neither would work on safari mobile.
I then moved my webpage over to IIS and the javascript works properly on Safari Mobile.
Just wondering if you might have an idea of what is causing this? Any info i can grab for you that might help? Maybe the Webserver is serving the pages up as HTTP/1.0 instead of 1.1. Is there a way to change that?
Thanks!
Adam
modified 18-Sep-20 11:52am.
Sign In
·
View Thread
if
(p.http_url.Equals (
"
/Test.png"
))
byte
[] buf = File.ReadAllBytes(
"
../../Test.png"
);
p.writeSuccessImage(buf.Length);
p.outputStream.Flush();
p.outputStream.BaseStream.Write(buf,
0
, buf.Length);
return
;
public
void
writeSuccessImage(
int
size)
outputStream.WriteLine(
"
HTTP/1.0 200 OK"
);
outputStream.WriteLine(
"
Date: "
+ DateTime.Now.ToUniversalTime().ToString(
"
r"
));
outputStream.WriteLine(
"
Last-Modified: Wed, 15 May 2019 09:22:32 GMT"
);
outputStream.WriteLine(
"
Accept-Ranges: bytes"
);
outputStream.WriteLine(
"
Content-Length: "
+ size.ToString());
outputStream.WriteLine(
"
Content-Type: image/png"
);
outputStream.WriteLine(
"
"
);
Sign In
·
View Thread
Can someone provide code to send json data in POST resquest
arunnnnnkumar
25-Jul-19 20:21
arunnnnnkumar
25-Jul-19 20:21
Can someone provide code to read json data from POST resquest body
modified 26-Jul-19 2:36am.
Sign In
·
View Thread
How to get this HTTP Server visible for the rest of the users of my Company ?
Member 14073310
7-Mar-19 11:57
Member 14073310
7-Mar-19 11:57
Hi David:
I just found your article about "Simple HTTP Server in C#" which will be very useful for my job.
Can you help me about how can I put this local HTTP server visible and public for the other users of my local Windows network in my Company, please ?
Thanks,
Norge Daniel
Smartraffic.com.m
Sign In
·
View Thread
You can use it to serve anything you like... but you'll need to do the work...
For example, with a single page application, your javascript will make server-side calls to fetch information when it needs it. you'll need to implement handlers for those server-side call URLs.
Sign In
·
View Thread
Hi, thanks for the excellent code. I am having an issue with different browsers when testing the server - Firefox works without any issues at all. As soon as I try IE or Edge, I get an IOException thrown in system.dll (which doesn't get caught in the try/catch block) when the read stream and flush calls are made. Additional information (edited) states: unable to read from the transport connection: did not respond after a period of time; also sometimes states unable to write to the transport connection - forcibly closed by the remote host.
I am yet to wireshark this to see what the differences between the browsers are, however are there any ideas in the meantime as to what could be occurring? Some research indicates that IE is known to send RST (tcp resets) during communications with servers which would explain this to a degree, but how to work around that?
modified 16-Feb-16 7:04am.
Sign In
·
View Thread
Hi there,
I really appreciate this simple server code since its pretty neat & effective. I'm trying out for a few days now, working on how to implement it. I'd like to use it to present a simple web form and process the data which is posted.
However I'm having an encoding issue:
Any non-ascii character that is posted through comes is in form of %C3%B6.
public
override
void
handlePOSTRequest(HttpProcessor p, StreamReader inputData)
var
enc = inputData.CurrentEncoding;
string
data = inputData.ReadToEnd();
inputData shows up as System.Text.UTF8Encoding .
But if I post smth. like öçşiğü ... йцукен then data comes up like
%C3%B6%C3%A7%C5%9Fi%C4%9F%C3%BC ... %D0%B9%D1%86%D1%83%D0%BA%D0%B5%D0%BD
The form page contains meta tag:
<
meta
charset
=
\
"
UTF-8\
"
>
So what is going on here? How can I properly get what is posted? I'll be really glad if you can help me on this.
Thank you very much..
Sign In
·
View Thread
Similar issue. If i use Stream.Read () and Stream.Write methods to dump an html file, it works but with images and icons, it doesn't. For icons and images i need ti use StreamReader.Read and StreamWriter.Write ()
Sign In
·
View Thread
The "meta charset" tag controls the character set the browser uses to interpret your page, not the way that form post data is sent.
Normally, form post data is sent to the server using multipart-mime encoding, so you'll need to read up on that and learn to decode it.
POST - HTTP | MDN
[
^
]
Sign In
·
View Thread
How can I use this on a windows form,like press start to start the server and all that information being shown on a textbox.pliz help
Sign In
·
View Thread
I'm not sure why I'm having trouble with this. I simply want to link a stylesheet and javascript file inside my HTML. I then of course need to capture the request in my HTTPProcessor. I'm currently getting the file bytes and pushing them to the outputstream after I send headers. Can you provide a quick example of how to do this because I apparently can't get something right.
Sign In
·
View Thread
Sure, I can extend the example with some static file assets (And a directory handler).
As for a quicker solution to your problem... Are you sending the proper mimetypes for the files? javascript needs to be "application/javascript", and CSS needs to be "text/css"..
https://developer.mozilla.org/en-US/docs/Incorrect_MIME_Type_for_CSS_Files
[
^
]
Sign In
·
View Thread
Here are some snippets of what I have so far. I have the "UI" folder being the root that contains the .js files, themes, etc. It appears to be fetching the file bytes correctly and pushing them to browser just doesn't render correctly. I know whatever it is it's something stupid I'm doing wrong. Any help would be appreciated.
string
url = p.http_url;
string
contentType = url.Contains(
"
."
) ? MimeType.Get(url.Split(
'
.'
)[url.Split(
'
.'
).Length-1]) :
"
text/html"
;
if
(contentType.StartsWith(
"
image/"
))
p.writeSuccess(contentType, GetImage(p, url));
else
if
(contentType ==
"
application/javascript"
||
contentType ==
"
text/css"
)
p.writeSuccess(contentType, GetFile(p, url.Substring(
0
, url.Length)));
*Above code abbreviated
private
byte[] GetFile(HTTPProcessor p,
string
url)
string
dir = Directory.GetCurrentDirectory();
dir +=
"
\\ui"
;
url = dir + url;
url = url.Replace(
"
/"
,
"
\\"
);
FileStream fs = File.Open(url, FileMode.Open);
BinaryReader br =
new
BinaryReader(fs);
byte[] bytes = br.ReadBytes((
int
)fs.Length);
br.Close();
fs.Close();
return
bytes;
public
void
writeSuccess(
string
content_type, byte[] data)
outputStream.WriteLine(
"
HTTP/1.0 200 OK"
);
outputStream.WriteLine(
"
Content-Type: "
+ content_type);
outputStream.WriteLine(
"
Accept-Ranges: bytes"
);
outputStream.WriteLine(
"
Content-Length: "
+ data.Length.ToString());
outputStream.WriteLine(
"
"
);
outputStream.BaseStream.Write(data,
0
, data.Length);
outputStream.BaseStream.Flush();
Sign In
·
View Thread
Remove the "Accept-Ranges: bytes", because you do not support partial byte ranges for content.
After that, I recommend you either put in a debug proxy, or use a telnet client to telnet directly to your server and manually enter a request. This will allow you to see exactly what your server is returning.
If you want me to look any further, you need to post the entire source to github, because I don't know what the rest of your code is doing.
Sign In
·
View Thread
Did you modify the sample yet? I too would be interested in how to do directory and static asset handling. Great work by the way!
Sign In
·
View Thread
Hi Joesnoopy,
I am facing the similar issue. Could you please share how you got rid of this?
Regards,
Samsudeen K S
Sign In
·
View Thread
Same issue. my method works good as far as I'm sending images, CSS scripts, HTML etc. JS doesn't seem to be loading in the browser.
It doesn't work with application/javascript as well.
switch (Path.GetExtension(url.ToLower()))
case ".htm": p.writeSuccess(); break;
case ".jpg":
case ".png":
case ".gif": p.writeSuccess("image/" + Path.GetExtension(url.ToLower())); break;
case ".js": p.writeSuccess("text/javascript"); break;
case ".css": p.writeSuccess("text/css"); break;
case ".ico": p.writeSuccess("image/x-icon"); break;
default: p.writeSuccess("application/octet-stream"); break;
Sign In
·
View Thread