I come across with Network Programming recently and find it really charming. Afer reading the book “Advanced Linux Programming”, I’m more curious about how to implement a basic http server or web server.
The source code of chapter 11 of “ALP” does tell the key idea, thus communicating through sockets between client and server, for HTTP compliance, just implement the HTTP protocal(RFC
2616). To know the things better, I’d like to know how to implement a core and basic HTTP server. You may know ‘Apache’, ‘NginX’ and ‘Lighttpd’ for a while, and they do lead the main tread. Basically, they are heavy enough to begin to know a basic HTTP server implementation.
After googling around, I find this link on stackoverflow is valuable and a good starting point to follow. From there, I decide to dig into mongoose. To walk through what mongoose does and provides, I will present some comments of core source code.
Be aware of the Protocal Stack
Many people get confused by HTTP, FTP, TCP/IP, SMTP, etc. To get all these stuff clear of your head, do please spend several miniutes to learn the Internet communication model, like the OSI 7-layer model or the TCP/IP 4-layer model.
Once you know the phsical, data-link, network, transportation, application what’s all about, trust me, you will find all the confused words gone:–)
HTTP Basics
A web server, basicly implements the HTTP protocol to define how the client interacts with the server, thus enabling the communication of varies of devices. But aware that HTTP is an application protocol, built upon TCP/IP which handles the real transportation of data, in the perspective of programming, that is Socket.
In a word, socket is the API of TCP/IP provided to programmers.
See my blog What Is Socket Anyway?.
Cross Platform development concerns
Wow, Mongoose works on Windows, Mac, UNIX/Linux, iPhone, Android eCos, QNX and many other platforms, what an amazing software!
Well, the basic idea behind cross platform development is to abstract another layer to adapt to the different implementations to the same task on different OS and platforms, my personal understanding, sorry:–)
When you see the source code of Mongoose, be prepared to come across with lots of #ifdef and #ifndef, etc. Things got really complicated when dealing with cross platform development, there are even specifed books and literatures talking about it.
OK I’m also newbie to this, so please RTFSC.
HTTP features kept in mind
As an embedded web server, Mongoose needs not to implement everything, just the core functionalities, however really powerful enough. Like CGI, SSI, SSL, Digest auth, Websocket, WEbDAV, Resumed download, URL rewrite, file blacklist, Custom error pages, Virtual hosts, IP-based ACL, Windows service, even Lua Server Pages.
As HTTP is an application protocol, so every feature is really related to the description in the RFCs along with many more extensions like HTTPS – HTTP over TLS.
Though many heavy web servers like Apache or Nginx does not implement everything of HTTP, so keep it simple and work.
The programming paradigm
There is not any class defined in Mongoose, though it can be compiled using C++, so the source code is pure C actually. The underlying dirty work will be handled by net_skeleton, like the management of socket connections and sending and receiving data packets.
To be clear, object is implemented through the use of struct, like mg_server, mg_connection. Get a good knowledge of C programming language before you decide to dig into Mongoose, period.
Mongoose is using the traditional select(), serving many clients with each thread, and using nonblocking I/O as illustrated in the function ns_server_poll implemented in net_skeleton.
Net Skeleton is a networking library written in C. It provides easy to use event-driven interface that allows to implement network protocols or scalable network applications with little effort. Net Skeleton releives developers from the burden of network programming complexity and let them concentrate on the logic. Net Skeleton saves time and money.
Core interfaces provided by net_skeleton
123456789101112131415161718192021222324
voidns_server_free(structns_server*);intns_server_poll(structns_server*,intmilli);voidns_server_wakeup(structns_server*);voidns_server_wakeup_ex(structns_server*,ns_callback_t,void*,size_t);voidns_iterate(structns_server*,ns_callback_tcb,void*param);structns_connection*ns_add_sock(structns_server*,sock_tsock,void*p);intns_bind(structns_server*,constchar*addr);intns_set_ssl_cert(structns_server*,constchar*ssl_cert);intns_set_ssl_ca_cert(structns_server*,constchar*ssl_ca_cert);structns_connection*ns_connect(structns_server*,constchar*host,intport,intssl,void*connection_param);intns_send(structns_connection*,constvoid*buf,intlen);intns_printf(structns_connection*,constchar*fmt,...);intns_vprintf(structns_connection*,constchar*fmt,va_listap);// Utility functionsvoid*ns_start_thread(void*(*f)(void*),void*p);intns_socketpair(sock_t[2]);intns_socketpair2(sock_t[2],intsock_type);// SOCK_STREAM or SOCK_DGRAMvoidns_set_close_on_exec(sock_t);voidns_sock_to_str(sock_tsock,char*buf,size_tlen,intflags);intns_hexdump(constvoid*buf,intlen,char*dst,intdst_len);
// Net skeleton interface// Events. Meaning of event parameter (evp) is given in the comment.enumns_event{NS_POLL,// Sent to each connection on each call to ns_server_poll()NS_ACCEPT,// New connection accept()-ed. union socket_address *remote_addrNS_CONNECT,// connect() succeeded or failed. int *success_statusNS_RECV,// Data has benn received. int *num_bytesNS_SEND,// Data has been written to a socket. int *num_bytesNS_CLOSE// Connection is closed. NULL};// Callback function (event handler) prototype, must be defined by user.// Net skeleton will call event handler, passing events defined above.structns_connection;typedefvoid(*ns_callback_t)(structns_connection*,enumns_event,void*evp);
Besides the interfaces provieded, there are lots of private functions to make everything possible, these are all implemented in net_skeleton.c as static functions.
Mongoose
Well, here finally comes the main character! As the dirty work has been done by net_skeleton, mongoose can focus on the real job.
// Server management functionsstructmg_server*mg_create_server(void*server_param,mg_handler_thandler);voidmg_destroy_server(structmg_server**);constchar*mg_set_option(structmg_server*,constchar*opt,constchar*val);intmg_poll_server(structmg_server*,intmilliseconds);constchar**mg_get_valid_option_names(void);constchar*mg_get_option(conststructmg_server*server,constchar*name);voidmg_set_listening_socket(structmg_server*,intsock);intmg_get_listening_socket(structmg_server*);voidmg_iterate_over_connections(structmg_server*,mg_handler_t,void*);voidmg_wakeup_server(structmg_server*);structmg_connection*mg_connect(structmg_server*,constchar*,int,int);// Connection management functionsvoidmg_send_status(structmg_connection*,intstatus_code);voidmg_send_header(structmg_connection*,constchar*name,constchar*val);voidmg_send_data(structmg_connection*,constvoid*data,intdata_len);voidmg_printf_data(structmg_connection*,constchar*format,...);intmg_websocket_write(structmg_connection*,intopcode,constchar*data,size_tdata_len);// Deprecated in favor of mg_send_* interfaceintmg_write(structmg_connection*,constvoid*buf,intlen);intmg_printf(structmg_connection*conn,constchar*fmt,...);constchar*mg_get_header(conststructmg_connection*,constchar*name);constchar*mg_get_mime_type(constchar*name,constchar*default_mime_type);intmg_get_var(conststructmg_connection*conn,constchar*var_name,char*buf,size_tbuf_len);intmg_parse_header(constchar*hdr,constchar*var_name,char*buf,size_t);intmg_parse_multipart(constchar*buf,intbuf_len,char*var_name,intvar_name_len,char*file_name,intfile_name_len,constchar**data,int*data_len);// Utility functionsvoid*mg_start_thread(void*(*func)(void*),void*param);char*mg_md5(charbuf[33],...);intmg_authorize_digest(structmg_connection*c,FILE*fp);structmg_expansion{constchar*keyword;void(*handler)(structmg_connection*);};voidmg_template(structmg_connection*,constchar*text,structmg_expansion*expansions);
// This structure contains information about HTTP request.structmg_connection{constchar*request_method;// "GET", "POST", etcconstchar*uri;// URL-decoded URIconstchar*http_version;// E.g. "1.0", "1.1"constchar*query_string;// URL part after '?', not including '?', or NULLcharremote_ip[48];// Max IPv6 string length is 45 characterscharlocal_ip[48];// Local IP addressunsignedshortremote_port;// Client's portunsignedshortlocal_port;// Local port numberintnum_headers;// Number of HTTP headersstructmg_header{constchar*name;// HTTP header nameconstchar*value;// HTTP header value}http_headers[30];char*content;// POST (or websocket message) data, or NULLsize_tcontent_len;// Data lengthintis_websocket;// Connection is a websocket connectionintstatus_code;// HTTP status code for HTTP error handlerintwsbits;// First byte of the websocket framevoid*server_param;// Parameter passed to mg_add_uri_handler()void*connection_param;// Placeholder for connection-specific datavoid*callback_param;// Needed by mg_iterate_over_connections()};structmg_server{structns_serverns_server;unionsocket_addresslsa;// Listening socket addressmg_handler_tevent_handler;char*config_options[NUM_OPTIONS];};// Local endpoint representationunionendpoint{intfd;// Opened regular local filestructns_connection*nc;// CGI or proxy->target connection};
Event handling
1234567891011
enummg_event{MG_POLL=100,// Callback return value is ignoredMG_CONNECT,// If callback returns MG_FALSE, connect failsMG_AUTH,// If callback returns MG_FALSE, authentication failsMG_REQUEST,// If callback returns MG_FALSE, Mongoose continues with reqMG_REPLY,// If callback returns MG_FALSE, Mongoose closes connectionMG_CLOSE,// Connection is closed, callback return value is ignoredMG_LUA,// Called before LSP page invokedMG_HTTP_ERROR// If callback returns MG_FALSE, Mongoose continues with err};typedefint(*mg_handler_t)(structmg_connection*,enummg_event);
Find something? Yes, mongoose is heavily using the underlying net_skeleton, as ns_server in mg_server suggests.
Or look at this:
Mongoose is just a wrapper for application built upon net_skeleton?
staticvoidns_add_to_set(sock_tsock,fd_set*set,sock_t*max_fd){if(sock!=INVALID_SOCKET){FD_SET(sock,set);if(*max_fd==INVALID_SOCKET||sock>*max_fd){*max_fd=sock;}}}intns_server_poll(structns_server*server,intmilli){structns_connection*conn,*tmp_conn;structtimevaltv;fd_setread_set,write_set;intnum_active_connections=0;sock_tmax_fd=INVALID_SOCKET;time_tcurrent_time=time(NULL);if(server->listening_sock==INVALID_SOCKET&&server->active_connections==NULL)return0;FD_ZERO(&read_set);FD_ZERO(&write_set);ns_add_to_set(server->listening_sock,&read_set,&max_fd);ns_add_to_set(server->ctl[1],&read_set,&max_fd);for(conn=server->active_connections;conn!=NULL;conn=tmp_conn){tmp_conn=conn->next;ns_call(conn,NS_POLL,¤t_time);ns_add_to_set(conn->sock,&read_set,&max_fd);if(conn->flags&NSF_CONNECTING){ns_add_to_set(conn->sock,&write_set,&max_fd);}if(conn->send_iobuf.len>0&&!(conn->flags&NSF_BUFFER_BUT_DONT_SEND)){ns_add_to_set(conn->sock,&write_set,&max_fd);}elseif(conn->flags&NSF_CLOSE_IMMEDIATELY){ns_close_conn(conn);}}tv.tv_sec=milli/1000;tv.tv_usec=(milli%1000)*1000;if(select((int)max_fd+1,&read_set,&write_set,NULL,&tv)>0){// Accept new connectionsif(server->listening_sock!=INVALID_SOCKET&&FD_ISSET(server->listening_sock,&read_set)){// We're not looping here, and accepting just one connection at// a time. The reason is that eCos does not respect non-blocking// flag on a listening socket and hangs in a loop.if((conn=accept_conn(server))!=NULL){conn->last_io_time=current_time;}}// Read possible wakeup callsif(server->ctl[1]!=INVALID_SOCKET&&FD_ISSET(server->ctl[1],&read_set)){unsignedcharch;recv(server->ctl[1],&ch,1,0);send(server->ctl[1],&ch,1,0);}for(conn=server->active_connections;conn!=NULL;conn=tmp_conn){tmp_conn=conn->next;if(FD_ISSET(conn->sock,&read_set)){conn->last_io_time=current_time;ns_read_from_socket(conn);}if(FD_ISSET(conn->sock,&write_set)){if(conn->flags&NSF_CONNECTING){ns_read_from_socket(conn);}elseif(!(conn->flags&NSF_BUFFER_BUT_DONT_SEND)){conn->last_io_time=current_time;ns_write_to_socket(conn);}}}}for(conn=server->active_connections;conn!=NULL;conn=tmp_conn){tmp_conn=conn->next;num_active_connections++;if(conn->flags&NSF_CLOSE_IMMEDIATELY){ns_close_conn(conn);}}//DBG(("%d active connections", num_active_connections));returnnum_active_connections;}
Feel something? Go dig yourself for much more fun:–)