This article analyzes in detail the method of implementing a simplest HTTP server based on C#. Share it for your reference. The details are as follows:
1. Introduction
This article uses C# to implement the simplest HTTP server class, which you can embed into your project, or you can read the code to learn about the HTTP protocol.
2. Background
High-performance WEB applications are generally built on powerful WEB servers, such as IIS, Apache, and Tomcat. However, HTML is a very flexible UI markup language, which means that any application and backend service can provide HTML generation support. In this small example, servers like IIS, Apache consume too much resources. We need to implement a simple HTTP server ourselves and embed it into our application to handle WEB requests. We only need one class to implement it, it's very simple.
3. Code implementation
First, let’s review how to use classes, and then we analyze the specific details of the implementation. Here we create a class inherited from HttpServer and implement two abstract methods: handleGETRequest and handlePOSTRequest:
public MyHttpServer(int port)
: base(port) {
}
public override void handleGETRequest(HttpProcessor p) {
("request: {0}", p.http_url);
();
("<html><body><h1>test server</h1>");
("Current Time: " + ());
("url : {0}", p.http_url);
("<form method=post action=/form>");
("<input type=text name=foo value=foovalue>");
("<input type=submit name=bar value=barvalue>");
("</form>");
}
public override void handlePOSTRequest(HttpProcessor p, StreamReader inputData) {
("POST request: {0}", p.http_url);
string data = ();
("<html><body><h1>test server</h1>");
("<a href=/test>return</a><p>");
("postbody: <pre>{0}</pre>", data);
}
}
When we start processing a simple request, we need to start a thread separately to listen for a port, such as port 8080:
Thread thread = new Thread(new ThreadStart());
();
If you compile and run this project, you will see the sample content generated on the page under the address of http://localhost:8080 in your browser. Let's take a brief look at how this HTTP server engine is implemented.
This WEB server consists of two components. One is responsible for starting the TcpListener to listen to the HttpServer class of the specified port, and uses the AcceptTcpClient() method to loop over TCP connection requests. This is the first step in handling TCP connections. Then the request arrives at the "specified" port, and a new pair of ports will be created to initialize the TCP connection from the client to the server. This pair of ports is the session of TcpClient, so that our main port can continue to receive new connection requests. From the following code, we can see that each time the listener will create a new TcpClien, the HttpServer class will create a new HttpProcessor, and then start a thread to operate. The HttpServer class also contains two abstract methods, and you must implement these two methods.
protected int port;
TcpListener listener;
bool is_active = true;
public HttpServer(int port) {
= port;
}
public void listen() {
listener = new TcpListener(port);
();
while (is_active) {
TcpClient s = ();
HttpProcessor processor = new HttpProcessor(s, this);
Thread thread = new Thread(new ThreadStart());
();
(1);
}
}
public abstract void handleGETRequest(HttpProcessor p);
public abstract void handlePOSTRequest(HttpProcessor p, StreamReader inputData);
}
In this way, a new tcp connection is processed by HttpProcessor in its own thread. The job of HttpProcessor is to correctly parse HTTP headers and control the correct implementation of abstract methods. Let's take a look at the HTTP header processing process. The first line of code for HTTP request is as follows:
After setting the input and output of process(), HttpProcessor will call the parseRequest() method.
String request = ();
string[] tokens = (' ');
if ( != 3) {
throw new Exception("invalid http request line");
}
http_method = tokens[0].ToUpper();
http_url = tokens[1];
http_protocol_versionstring = tokens[2];
("starting: " + request);
}
HTTP requests are composed of 3 parts, so we only need to use the () method to divide them into 3 parts. The next step is to receive and parse HTTP header information from the client. Each line of data in the header information is saved in the form of Key-Value (key-value). The blank line represents the end flag of the HTTP header information. In our code, we use the readHeaders method to read HTTP header information:
("readHeaders()");
String line;
while ((line = ()) != null) {
if (("")) {
("got headers");
return;
}
int separator = (':');
if (separator == -1) {
throw new Exception("invalid http header line: " + line);
}
String name = (0, separator);
int pos = separator + 1;
while ((pos < ) && (line[pos] == ' ')) {
pos++; // Filter out all spaces
}
string value = (pos, - pos);
("header: {0}:{1}",name,value);
httpHeaders[name] = value;
}
}
By this point, we have learned how to handle simple GET and POST requests, which are assigned to the correct handler handler, respectively. In this example, there is a difficult problem to be dealt with when sending data, that is, the request header information contains the length information of the sent data content-length. When we hope that the handlePOSTRequest method in the subclass HttpServer can process the data correctly, we need to put the data length content-length information into the data stream together, otherwise the sender will wait for data that will never reach and block. We used a less elegant but very efficient way to handle this situation, which reads the data into the MemoryStream before sending it to the POST processing method. This approach is not ideal for the following reasons: If the data sent is large, or even uploading a file, it is not appropriate or even impossible for us to cache the data in memory. The ideal way is to limit the length of the post, for example, we can limit the length of the data to 10MB.
Another simplified part of this simple version of HTTP server is the return value of content-type. In the HTTP protocol, the server will always send the MIME-Type of the data to the client, telling the client what type of data it needs to receive. In the writeSuccess() method, we see that the server always sends the text/html type. If you need to add other types, you can extend this method.
I hope this article will be helpful to everyone's C# programming.