UHTTPD Source Code Analysis

Source: Internet
Author: User
Tags goto set time

UHTTPD is the default Web server on OpenWrt, supports Cgi,lua scripts, and services for static files. It is a streamlined server that is generally suitable for use as an embedded device such as a router, or a Web server for getting started.

UHTTPD source can use SVN to download here.

Overview

Uhttpd.png
First, when the UHTTPD is started, it reads the parameters first, and then configures the server. Parameters can be entered by the command line, where the port parameter must be set and the others have default values.

When the parameters are configured, the server enters Uh_mainloop and waits for the request.

In the main loop, UHTTPD uses select for polling, rather than using fork for concurrency, which in some way reduces concurrency, but is well suited for small servers like this.

When select detects a client request, UHTTPD accepts the request before parsing, and then calls Uh_dispatch_request to distribute the request. Where LUA requests are special and not distributed by Uh_dispatch_request.

In the distribution process (excluding LUA requests), a CGI request or a static file request is judged based on the prefix of path, and the default CGI prefix is/cgi-bin. The CGI request enters the uh_cgi_request, and the file requests to enter the Uh_file_request,lua request will enter the Lua_request.

In three kinds of handler, the request is processed. Lua_request calls the LUA interpreter for processing, file_request directly reads the file and returns, and the CGI request is more complex, which is explained later.

After three request processing, it will be returned to the client. The loop ends at this time.

Start Server Configuration

The main function of the startup portal is located in UHTTPD.C, which accepts command-line arguments, configures the server, and starts the server. Let's take a look at what the configuration is.

Help

Where port must be specified, and everything else has a default value. In general, we can use this parameter to start the server:

1 ./uhttpd-p 8080

The server is running in the background by default and you can use the Ps-a | grep uhttp "See it running, using Nmap to scan the local port can also see that it is already listening on port 8080."
Nmap

The configuration of the uhttpd is stored with "struct config", and the members are also rich:

123456789101112131415161718192021222324252627282930313233343536373839 struct Config {Char Docroot[path_max];Char *realm;Char *file;Char *index_file;Char *error_handler;int no_symlinks;int no_dirlists;int network_timeout;int rfc1918_filter;int tcp_keepalive;#ifdef have_cgiChar *cgi_prefix;#endif #ifdef Have_luaChar *lua_prefix;Char *lua_handler;Lua_state *lua_state;lua_state * (*lua_init) (Const char *handler); void (*lua_close) (Lua_state *l);void (*lua_request) (struct client *cl, struct http_request *req, lua_state *l); #endif #if defined (have_cgi) | | | defined (HAVE_LUA) int script_timeout;#endif #ifdef HAVE_TLSChar *cert;Char *key;Ssl_ctx *tls; Ssl_ctx * (*tls_init) (void);Int (*tls_cert) (Ssl_ctx *c, const char *file); Int (*tls_key) (Ssl_ctx *c, const char *file); void (*tls_free) (struct listener *l); Int (*tls_accept) (struct client *c); void (*tls_close) (struct client *c); Int (*tls_recv) (struct client *c, void *buf, int len); Int (*tls_send) (struct client *c, void *buf, int len); #endif };
Port bindings

The port binding of the server is not written in config, but the port is bound directly with Uh_socket_bind.

12345 / * BIND sockets */bound + = Uh_socket_bind (&serv_fds, &max_fd, bind[0]? bind:null, port,&h INTs, (opt = = ' s '), &conf);

Uh_socket_bind:

1234 static int uh_socket_bind (fd_set *serv_fds, int *max_fd, const Char *host, const Char *port,struct addrinfo *hints, int do_tls, struct config *conf )

This function makes port bindings and adds listener to a global list, so we can bind multiple ports.

123456 / * Add listener to Global list * / if (! (L = uh_listener_add (sock, conf))) {fprintf (stderr, "Uh_listener_add (): Failed to allocate memory\n"); goto error;}

Interestingly, UHTTPD has some information in the list and inserts it into the table header with the Add function. C doesn't have a ready-made set of frames, but it's easy to write a list of your own. These tool functions are all in UHTTPD-UTILS.C.

1234 static struct client *uh_clients = NULL; struct Client * uh_client_add (int sock, struct listener *serv); struct Client * uh_client_lookup (int sock); void Uh_client_remove (int sock);
Configuration file

It must be too cumbersome to use the command line, UHTTPD can also be configured with a configuration file.

12 / * Config file */Uh_config_parse (&conf);

But the options for the profile don't seem to be many, and the best way to do this is to write a startup script.

Official launch

After a series of configurations, UHTTPD is finally officially launched. It defaults to the background start, fork a child process, the parent process exits, and the child process with the configuration file and the server FD into the Mainloop.

12 / * Server main loop */Uh_mainloop (&conf, Serv_fds, MAX_FD);
Wait for request

The Uh_mainloop function is also in the uhttp.c, and the outermost layer is a large loop.

1234567 While (run) {if (select (Max_fd + 1, &read_fds, NULL, NULL, NULL) = =-1) {...} ...}
Select

The Select function acts as a blocking request, and with accept, it uses a polling mechanism rather than fork, which is more suitable for embedded devices.

12345 if (select (Max_fd + 1, &read_fds, NULL, NULL, NULL) = =-1) {perror ("select ()"); exit (1);}

The last parameter is the set time-out and, if set to null, an infinite timeout until the FD is changed.

GET request

It then enters a nested complex "if-else" statement, counting the deepest six-layer if nesting. The main function is to traverse all the FD, find the server and the client FD, on the server, accept and add the client to the list. In the client's FD, the request is processed.

After using UH_HTTP_HEADER_RECV to obtain the request, a http_request structure is obtained with uh_http_header_parse parsing.

12345678 struct Http_request {intmethod; float version; int redirect_status; Char *url; Char *headers[uh_limit_headers]; struct Auth_realm *realm;};
Request distribution

Once the http_request is obtained, the requested distribution can be made according to the URL. Give lua_request with the LUA prefix, otherwise hand it over to uh_dispatch_request.

1234567891011121314 / * Lua request? * / if (conf->lua_state && uh_path_match (Conf->lua_prefix, Req->url)) {Conf->lua_request (cl, req, conf->lua_state);} Else / * Dispatch request * / if (pin = Uh_path_lookup (cl, req->url))! = NULL) {/* auth OK? */if (!pin->redirected && Uh_auth_check (cl, req, pin))Uh_dispatch_request (cl , req, pin);}

Dispatch just made a simple judgment and handed it to the next level.

123456789 if (Uh_path_match (Cl->server->conf->cgi_prefix, pin->name) | | (IPR = Uh_interpreter_lookup (Pin->phys))) {Uh_cgi_request (cl, req, PIN, IPR);} Else {Uh_file_request (cl, req, pin);}
Processing requests

Lua asked for a moment, just to say file and CGI requests.

static files

File_request in the uhttpd-file.c. Members are as follows

1234567891011121314151617181920212223 UHTTPD-FILE.C macro _xopen_source _bsd_source function uh_file_mime_lookup uh_file_mktag Uh_file_date2unix uh_file_ Unix2date uh_file_header_lookup Uh_file_response_ok_hdrs uh_file_response_200 uh_file_response_304 uh_file_response _412 uh_file_if_match uh_file_if_modified_since uh_file_if_none_match uh_file_if_ Range Uh_file_if_unmodified_since uh_file_scandir_filter_dir uh_file_dirlist uh_file_request

Since it is a static file request, it is natural to first look at the local there is no this file, some words on the read content to the client, no 404.

Here's an interesting function,

123456 / * Test preconditions * / if (OK) ensure_out (Uh_file_if_modified_since (cl, req, &pi->stat, &ok)); if (OK) ensure_out (Uh_file_if_match (cl, req, &pi->stat, &ok)); if (OK) ensure_out (Uh_file_if_range (cl, req, &pi->stat, &ok)); if (OK) ensure_out (Uh_file_if_unmodified_since (cl, req, &pi->stat, &ok)); if (OK) ensure_out (Uh_file_if_none_match (cl, req, &pi->stat, &ok));

Ensure_out is used extensively during the processing of requests, which should be guaranteed to close the FD. If the network is abnormal or the file read/write exception, it is necessary to ensure that the FD is closed correctly. Implementation is simple, a class function macro is done.

123456 #define ensure_out (x) \ Do { if ((x) < 0) goto out;} While (0) Out:if (fd >-1)Close (FD);
CGI Request

Processing CGI Requests is a little more complicated.

The Uh_cgi_request function is located in UHTTPD-CGI.C. Members are as follows.

123456 uhttpd-cgi.c function Uh_cgi_header_parse uh_cgi_header_lookup uh_cgi_error_500 uh_cgi_request

Although there are few members, there are still more than 600 rows in total.

The process of CGI is basically to call the CGI program, get its processing results, and then return to the client. But the CGI program and the main melody function are definitely two processes, how they communicate between them, how to pass the data, this is the key.

UHTTPD uses a pipeline and CGI program to communicate, there are two of pipelines to achieve two-way communication. A pipeline is responsible for writing data from the parent process to the CGI program, primarily the client's post data. The other is to read the processing results of the CGI program. At the same time, according to the CGI standard, the HTTP request header is passed through the environment variable to the CGI program, the CGI program is the fork and the exec, therefore inherits the environment variable, achieves the goal which transmits the data.

In a subprocess, a redirect is made with dup2 and the input and output streams are directed to the pipeline.

123 / * Patch stdout and stdin to pipes */dup2 (rfd[1], 1); Dup2 (wfd[0], 0);

The environment variable is then set with a large section of code.

123456 Setenv ("SERVER_NAME", Sa_straddr (&CL->SERVADDR), 1); Setenv ("Server_addr", Sa_straddr (&CL->SERVADDR), 1); Setenv ("Server_port", Sa_strport (&CL->SERVADDR), 1); Setenv ("Remote_host", Sa_straddr (&CL->PEERADDR), 1); Setenv ("REMOTE_ADDR", Sa_straddr (&CL->PEERADDR), 1); Setenv ("Remote_port", Sa_strport (&CL->PEERADDR), 1);

To actually invoke the CGI program.

1234 if (IP! = NULL)execl (Ip->path, Ip->path, Pi->phys, NULL); Elseexecl (Pi->phys, Pi->phys, NULL);

At the same time, the parent process anxiously waits for an echo to the other end of the pipe. It waits to wait for the impatient, so it again mechanism to set itself a timeout, after this time it left.

12345678910111213 Ensure_out (rv = select_intr (Fd_max, &reader, (Content_length >-1), &writer:null, NULL, &timeout)); /c1> ... / * read it from socket ... */ensure_out (Buflen = Uh_tcp_recv (cl, buf,min (content_length, sizeof (BUF))); ..... / * ... and write it to child ' s stdin * / if (write (wfd[1], buf, Buflen) < 0)perror ("write ()"); ...... / * Read Data from the child ... */ if ((Buflen = read (rfd[0], buf, sizeof (BUF))) > 0)

After reading the data from the CGI program, it is still not reassuring, but also resolved the response header, confirmed correctly, before the client.

This is the end of the process.

Summarize

The first time to look at the source of the server, so found a relatively simple server. Generally understand its principle, but a lot of details still do not understand, perhaps only oneself to realize can have a deep understanding of it. There is not much code in UHTTD, many of which are used to handle errors, and it is important to handle exceptions. If you have a chance, you want to implement a server yourself.

UHTTPD Source Code Analysis

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.