Nginx spdy patch implementation

Source: Internet
Author: User

Not long ago, nginx officially released the spdy patch. So far it has not been merged into the nginx source code, mainly because the patch is far from mature and the code and functions are not complete enough. Spdy
It takes some time for patches to be merged into the nginx source code. This article is based on the current patch. Let's take a look at how nginx officially implements spdy.

It is the general process for nginx to process a request. Here we only draw a simple model, and the actual process is quite complicated. The red part in the figure is the main content of spdy patch.
For common HTTP requests, nginx uses a state machine to parse HTTP protocol streaming. you do not have to collect the number of bytes before parsing.) Finally, all the data in the request is stored in a request object. In the subsequent handler module, You can process the request object. After the processing of a request is completed, the filter module at a layer will respond to the request.
In the face of a spdy request, the overall nginx processing process is the same as the HTTP request. The main difference is that the request parser has changed, however, the parsing result of spdy is completely stored in the request object used by common HTTP, and a new spdy request object is not introduced, this is like transparently converting a spdy request to an HTTP request. Only by converting spdy requests to HTTP requests can all handler and filter modules work normally. Since the response after the handler and filter modules is an HTTP response, a spdy must be introduced.
The filter module assembles the HTTP Response content into a spdy frame message to respond to the client.
Next, let's continue to look at the details of the spdy parsing process. The spdy filter module is simple to do, and this article will be ignored for the moment.
Readers who have read the nginx source code should know that when the client sends a request to nginx, nginx will first call the ngx_http_init_request function, which focuses on the following code snippet:

#if (NGX_HTTP_SSL)    {    ngx_http_ssl_srv_conf_t  *sscf;    sscf = ngx_http_get_module_srv_conf(r, ngx_http_ssl_module);    if (sscf->enable || addr_conf->ssl) {        if (c->ssl == NULL) {            c->log->action = "SSL handshaking";            if (addr_conf->ssl && sscf->ssl.ctx == NULL) {                ngx_log_error(NGX_LOG_ERR, c->log, 0,                              "no \"ssl_certificate\" is defined "                              "in server listening on SSL port");                ngx_http_close_connection(c);                return;            }#if (NGX_HTTP_SPDY)            if (addr_conf->spdy) {                r->spdy_stream = (void *) 1; //FIXME            }#endif            if (ngx_ssl_create_connection(&sscf->ssl, c, 0) // NGX_SSL_BUFFER) FIXME                != NGX_OK)            {                ngx_http_close_connection(c);                return;            }            rev->handler = ngx_http_ssl_handshake;        }        r->main_filter_need_in_memory = 1;    }    }#endif

Since spdy enforces SSL, you must follow this logic and enter the ngx_http_ssl_handshake process to complete SSL handshake authentication. These processes are only common HTTPS requests that need to be completed in steps. After the SSL handshake is complete, how does nginx determine whether https or spdy is used for this request? See the following code:

Static voidngx_http_ssl_handshake_handler (ngx_connection_t * c) {ngx_http_request_t * r; r = C-> data; # If (ngx_http_spdy) r-> spdy_stream = NULL; // fixme # endif if (c-> SSL-> handshaked) {/** the majority of browsers do not send the "close policy" alert. * among them are MSIE, old Mozilla, Netscape 4, Konqueror, * and links. and what is more, MSIE ignores the server's alert. ** opera and recent Mozilla Send the alert. */C-> SSL-> no_wait_shutdown = 1; # If (ngx_http_spdy) {unsigned Len; const u_char * data; // This is where the spdy protocol is obtained using TLS-NPN extensions, it is also the entry for processing spdy requests. // As mentioned in spdy introduction, it is a TLS extension protocol specially developed by Google for spdy deployment. Ssl_get0_next_proto_negotiated (c-> SSL-> connection, & Data, & Len); If (LEN = sizeof ("spdy/2")-1 & ngx_memcmp (data, "spdy/2", sizeof ("spdy/2")-1) = 0) {# If (ngx_stat_stub) (void) ngx_atomic_fetch_add (ngx_stat_reading,-1 ); (void) ngx_atomic_fetch_add (ngx_stat_requests,-1); # endif C-> DATA = C; // fixme // initialize spdy to start the spdy processing process. Ngx_http_init_spdy (c-> Read); Return ;}# endif C-> log-> action = "reading client request line"; C-> Read-> handler = ngx_http_process_request_line; /* stub: epoll edge */C-> write-> handler = ngx_http_empty_handler; handler (c-> Read); return;} ngx_http_close_request (R, ngx_http_bad_request); return ;}

After the functional SSL handshake is complete, the entry function for real request data processing starts. After being recognized as spdy by ssl_get0_next_proto_negotiated, The spdy processing process begins. Let's take a look at the key code lines at the end of ngx_http_init_spdy.

Voidngx_http_init_spdy (ngx_event_t * rev ){//............................... At the beginning, zlib is basically related to initialization, which prepares for compression and decompression of requests and responses. //// The Event Callback Function is mounted here. Next we will focus on the callback mounted on the read event. This process will complete parsing the spdy request. Rev-> handler = ngx_http_spdy_read_handler; C-> write-> handler = ngx_http_spdy_write_handler; ngx_http_spdy_read_handler (REV );}

During spdy Request Parsing, The ngx_http_spdy_read_handler callback function is always mounted on the read event rev. The function is of course to read data from the socket when the read event occurs, then, the data is handed over to the spdy parser. The ngx_http_spdy_read_handler function contains the following code snippet:

Do {// SC is a ngx_http_spdy_connection_t structure, abstracted as a TCP connection. The handler // called here is the spdy parser mounted at the moment. Different parser will be mounted for different frame messages. Rc = SC-> handler (SC, & P, n); n = end-P; If (rc = ngx_again) {ngx_memcpy (SC-> buffer, P, break); break;} If (rc = ngx_error) {ngx_log_error (ngx_log_warn, C-> log, 0, "spdy error"); ngx_http_spdy_finalize_connection (SC, ngx_http_internal_server_error ); return ;}} while (N );

The spdy parser mentioned in this article is used to process different frame functions of spdy. If you are not familiar with frame, you can refer to my Google spdy introduction. The frame processing entry is ngx_http_spdy_process_frame:

Static compile (ngx_http_spdy_connection_t * SC, u_char ** POs, size_t size) {u_char * P, flags; size_t length; uint32_t head; ngx_http_spdy_stream_t * stream; // The 8-byte header information of the frame is not fully read, and the returned result is to continue waiting for data. This is not stream parsing, because the parsing process starts only after 8 bytes are satisfied. If (size <8) {return ngx_again;} p = * Pos; // convert the first 4 bytes into an integer and then compare and judge, because the first 4 bytes determine the frame type. # If (ngx_have_nonaligned) Head = * (uint32_t *) P; # else head = P [0] <24 | P [1] <16 | P [2] <8 | P [3]; # endif // take the 5th-byte flags domain and the last 3-byte length domain. Flags = P [4]; length = ngx_spdy_frame_parse_len (p + 5); SC-> length = length; SC-> flags = flags; ngx_log_debug3 (ngx_log_debug_http, SC-> connection-> log, 0, "spdy process frame head: % UI F: % UI L: % UI", Head, flags, length ); * POS + = 8; // Mount different frame Resolvers Based on the frame type. All the cases listed in the switch are control frames. There are so many todo items that we can see that the spdy patch code is not complete yet. Switch (head) {Case ngx_spdy_syn_stream_head: SC-> handler = handler; return ngx_ OK; case when: // todo log return ngx_error; Case ngx_spdy_rst_stream_head: SC-> handler = handler; return ngx_ OK; Case ngx_spdy_settings_head: // todo SC-> handler = ngx_http_spdy_skip_frame; return ngx_ OK; Case ngx_spdy_noop_head: If (flags! = 0 | length! = 0) {// todo log return ngx_error;} return ngx_ OK; Case ngx_spdy_ping_head: SC-> handler = ngx_http_spdy_process_ping; return ngx_ OK; Case ngx_spdy_goaway_head: // todo SC-> handler = ngx_http_spdy_skip_frame; return ngx_ OK; Case ngx_spdy_headers_head: // todo log return ngx_error;} // The received frame is not a control frame but a data frame, so the data frame logic is started below. Head = ntohl (head); // determines whether the flag of the control and data frame is 0. If it is not 0, the message is invalid. You need to ignore this frame message. If (head> 31) {// todo version & type check ngx_log_debug1 (ngx_log_debug_http, SC-> connection-> log, 0, "spdy unknown frame % UI", head ); SC-> handler = ngx_http_spdy_skip_frame; return ngx_ OK;} // determine which stream the data frame belongs to based on the SID in the header. Stream = ngx_http_spdy_get_stream_by_id (SC, head); If (Stream = NULL | stream-> request-> discard_body) {SC-> handler = ngx_http_spdy_skip_frame; return ngx_ OK ;} if (Stream-> half_closed) {// todo log & error handling return ngx_error;} // mount the data frame parser and start parsing the data frame. SC-> stream = stream; SC-> handler = ngx_http_spdy_process_data_frame; return ngx_http_spdy_process_data_frame (SC, POs, size-8); // fixme}

The frame judgment is completed here. This is the entrance to all frames. The next step is to execute a frame parser to parse the specific frame. Syn_stream Control Frame creates a stream and starts to initiate a request. Let's take a look at the processing process of this frame. Other frames will not be analyzed in this article.

Static struct (ngx_http_spdy_connection_t * SC, u_char ** POs, size_t size) {u_char * P; ngx_uint_t Sid, PRIO, index; ngx_http_cleanup_t * CLN; ngx_http_request_t * r; ngx_http_spdy_stream_t * stream; ngx_http_spdy_srv_conf_t * SSCF; // another place where data must be collected and processed. The 10 bytes here are stream-ID, associated-to-stream-ID, and priority. If (size <10) {return ngx_again;} p = * Pos; SC-> length-= 10; * POS + = 10; // parses the priority of stream IDs and streams. SID = ngx_spdy_frame_parse_sid (p); PRIO = P [5]> 2; ngx_log_debug2 (ngx_log_debug_http, SC-> connection-> log, 0, "spdy syn_stream frame Sid: % ui prio: % UI ", Sid, PRIO); SSCF = ngx_http_get_module_srv_conf (SC-> default_request, ngx_http_spdy_module ); // SSCF-> concurrent_streams is the maximum number of concurrent streams allowed for each connection specified in the configuration file. If (SC-> Processing = SSCF-> concurrent_streams) {values (SC, Sid, ngx_spdy_refused_stream); SC-> handler = ngx_http_spdy_skip_headers; return ngx_ OK ;} // create a common HTTP request object. R = ngx_http_spdy_create_request (SC); If (r = NULL) {return ngx_error;} // create a new stream. Stream = ngx_pcalloc (R-> pool, sizeof (ngx_http_spdy_stream_t); If (Stream = NULL) {return ngx_error;} r-> spdy_stream = stream; stream-> id = Sid; stream-> request = r; stream-> connection = SC; stream-> priority = PRIO; // note the following, if fin_flag is set for the flags field, the stream will not receive the data frame, that is, a common http get operation with no body data. Set stream to half close. Stream-> half_closed = SC-> flags & ngx_spdy_flag_fin ;........................ SC-> stream = stream; // parse to start parsing headers. SC-> handler = ngx_http_spdy_process_headers; return ngx_ OK ;}

Now we start to parse headers. The specific parsing process is not analyzed here. Interested readers can analyze the Code against the spdy draft document. Let's take a look at the last few lines of code ngx_http_spdy_process_headers:

Static struct (ngx_http_spdy_connection_t * SC, u_char ** POs, size_t size) {int Z; ngx_buf_t * Buf; ngx_int_t RC; ngx_uint_t last; ngx_table_elt_t * h; ngx_connection_t * C; ngx_http_request_t * r ;............................. SC-> processing ++; // the request will be executed here. Let's take a look at the process in detail. Ngx_http_spdy_run_request (r); // After the frame is processed, reset the spdy parser as the frame judgment entry callback function and prepare to process the next frame. SC-> handler = handler; return ngx_done;} static handler (handler * r) {ngx_uint_t I; ngx_list_part_t * part; ngx_table_elt_t * h; ngx_connection_t * fc; handler * hh; ngx_http_core_main_conf_t * cmcf; // construct a normal HTTP request line based on the spdy request data. This is the conversion process from spdy to HTTP. If (ngx_http_spdy_construct_request_line (r )! = Ngx_ OK) {ngx_http_spdy_finalize_request (R, ngx_http_internal_server_error ); return ;}.................................... R-> http_state = ngx_http_process_request_state; // the header is being converted to HTTP. If (ngx_http_process_request_header (r )! = Ngx_ OK) {return;} If (R-> plain_http) {ngx_log_error (ngx_log_info, FC-> log, 0, "client sent plain HTTP request to HTTPS port "); aggregate (R, ngx_http_to_https); Return ;}# if (ngx_stat_stub) (void) aggregate (ngx_stat_reading,-1); R-> stat_reading = 0; (void) aggregate (ngx_stat_writing, 1); R-> stat_writing = 1; # endif R-> write_event_handler = ngx_http_cor E_run_phases; // start to execute the request, mainly to execute all the phases stages. Here we start to enter the General HTTP request process. // The next step is to go through all the handler, filter, and other module processes. Ngx_http_core_run_phases (r); ngx_http_run_posted_requests (FC );}


The implementation of spdy patch is still very clear, with two main objectives: 1. parsing spdy frame; 2. Converting to HTTP request. From the code, we can see that this patch is only a preliminary implementation, and there are still a lot of imperfections. It is not surprising that there are still major adjustments in the implementation of nginx official website. Of course, spdy's server push and other complex and powerful functions have not yet been implemented in this patch.

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: 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.