Nginx SPDY 패 치 구현

얼마 전 Nginx 는 공식 적 으로 SPDY 의 패 치 를 내 놓 았 습 니 다. 아직 nginx 소스 코드 에 합병 되 지 않 았 습 니 다. 주로 이 패 치 가 아직 성숙 하지 않 고 코드 와 기능 이 완선 되 지 않 았 기 때 문 입 니 다.개인 적 으로 spdy patch 가 nginx 소스 코드 에 합 쳐 진 지 아직 멀 었 다 고 생각 합 니 다.본 고 는 현재 의 패 치 를 바탕 으로 nginx 정부 가 어떻게 spdy 를 실현 하고 있 는 지 엿 보 는 것 이다.
위의 그림 은 nginx 가 요청 한 대체적인 절 차 를 처리 하 는 것 입 니 다. 여 기 는 간단 한 모델 만 그 렸 을 뿐 실제 과정 은 상당히 복잡 합 니 다.그림 의 빨간색 부분 은 바로 SPDY patch 의 주요 내용 이다.
일반적인 http 요청 에 대해 nginx 는 상태 기 를 사용 하여 http 프로 토 콜 을 흐 르 게 해석 합 니 다.마지막 으로 요청 한 모든 데 이 터 를 하나의 request 대상 에 저장 합 니 다. 다음 handler 모듈 에서 이 request 대상 을 처리 할 수 있 습 니 다. 요청 한 처리 작업 이 완료 되면 한 층 한 층 의 filter 모듈 을 통 해 응답 을 보 냅 니 다.
spdy 요청 에 직면 하여 nginx 전체 처리 절 차 는 http 요청 과 마찬가지 로 요청 한 해석 기 가 바 뀌 었 지만 spdy 의 분석 결과 도 일반 http 에서 사용 하 는 request 대상 에 데 이 터 를 완전히 저장 하고 새로운 spdy request 대상 을 도입 하지 않 았 습 니 다. 이 는 spdy 요청 을 http 요청 으로 투명 하 게 전환 하 는 것 과 같 습 니 다. spdy 요청 만 있 습 니 다.http 요청 으로 전환 해 야 현재 모든 handler, filter 모듈 이 정상적으로 작 동 할 수 있 습 니 다. handler, filter 모듈 을 거 친 응답 은 http 의 응답 이기 때문에 spdy filter 모듈 을 도입 하여 http 응답 내용 을 spdy 프레임 메시지 로 조립 하여 클 라 이언 트 에 응답 해 야 합 니 다.
다음은 SPDY 분석 처리 과정의 세부 사항 을 살 펴 보 겠 습 니 다. spdy filter 모듈 이 하 는 일이 비교적 간단 하기 때문에 본 고 는 잠시 무시 하 겠 습 니 다.
nginx 소스 코드 를 본 독자 들 은 클 라 이언 트 가 nginx 에 요청 을 보 낼 때 nginx 가 먼저 호출 되 는 함 수 는 ngx http init request 라 는 것 을 알 고 있 을 것 입 니 다. 이 함수 에서 다음 코드 세그먼트 에 중점 을 두 고 있 습 니 다.
#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

spdy 는 ssl 을 강제 하기 때문에 이 논 리 를 걸 어야 합 니 다. 그리고 ngx http ssl handshake 과정 에 들 어가 ssl 의 악수 인증 을 완성 해 야 합 니 다. 이 과정 은 일반적인 https 요청 일 뿐 입 니 다. 전체 ssl 악수 가 완 료 된 후에 nginx 는 이 요청 을 할 때 https 인지 spdy 인지 어떻게 판단 합 니까? 다음 코드 를 보 세 요.
static void
ngx_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 notify" 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;

//         TLS-NPN    spdy     ,  spdy       。
// NPN spdy       , google   spdy         tls    。
        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

//    spdy,       spdy    。
            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;

        ngx_http_process_request_line(c->read);

        return;
    }

    ngx_http_close_request(r, NGX_HTTP_BAD_REQUEST);

    return;
}

이 함수 식 ssl 악수 가 완료 되면 실제 요청 데이터 처리 의 입구 함수 에 들 어가 기 시작 합 니 다.
SSL get0 next proto negotiated 가 spdy 로 인식 되면 서 spdy 처리 과정 에 들 어가 기 시 작 했 습 니 다. ngx http init spdy 의 마지막 핵심 코드 를 생략 하고 보 세 요.
void
ngx_http_init_spdy(ngx_event_t *rev)
{
  //
    。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
              zlib  ,                。
  //

//            ,                 ,       spdy     。
    rev->handler = ngx_http_spdy_read_handler;
    c->write->handler = ngx_http_spdy_write_handler;

    ngx_http_spdy_read_handler(rev);
}

spdy 가 분석 을 요청 하 는 과정 에서 읽 기 이벤트 rev 에 ngx http spdy read handler 라 는 반전 함수 가 항상 마 운 트 되 어 있 습 니 다. 그 역할 은 당연히 사건 이 발생 했 을 때 socket 에서 데 이 터 를 읽 은 다음 spdy 해상도 기 에 데 이 터 를 전달 하 는 것 입 니 다. ngx http spdy read handler 편지 수 에는 다음 과 같은 코드 세그먼트 가 있 습 니 다.
        do {
// sc    ngx_http_spdy_connection_t  ,      tcp  。     handler
//        spdy   。   frame              。
            rc = sc->handler(sc, &p, n);

            n = end - p;

            if (rc == NGX_AGAIN) {
                ngx_memcpy(sc->buffer, p, NGX_SPDY_STATE_BUFFER_SIZE);
                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);

본 논문 에서 언급 한 spdy 해석 기 는 spdy 의 서로 다른 frame 함 수 를 처리 하 는 것 입 니 다. frame 을 모 르 는 사람 은 제 가 쓴 Google spdy 소 개 를 보십시오. frame 처리 의 입 구 는 ngx http spdy process frame 입 니 다.
static ngx_int_t
ngx_http_spdy_process_frame(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;

// frame 8            ,         。         ,         8          。
    if (size < 8) {
        return NGX_AGAIN;
    }

    p = *pos;

//   4                ,   4     frame   。
#if (NGX_HAVE_NONALIGNED)
    head = *(uint32_t *) p;
#else
    head = p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3];
#endif

//   5   flags    3       。
    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;

//       frame   ,     frame   。switch    case   control frame。        TODO,     spdy patch       。
    switch (head) {

    case NGX_SPDY_SYN_STREAM_HEAD:
        sc->handler = ngx_http_spdy_process_syn_stream;
        return NGX_OK;

    case NGX_SPDY_SYN_REPLY_HEAD:
        //TODO log
        return NGX_ERROR;

    case NGX_SPDY_RST_STREAM_HEAD:
        sc->handler = ngx_http_spdy_process_rst_stream;
        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;
    }

//        frame  control frame,  data frame,      data frame   。

    head = ntohl(head);

//   control data frame       0,  0       ,     frame  。
    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;
    }

//      sid   data frame    stream。 
    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;
    }

//   data frame   ,      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
}

이 쯤 되면 frame 의 판단 이 완 료 됩 니 다. 이것 은 모든 frame 의 입구 입 니 다. 다음은 frame 해상도 기 를 실행 하여 구체 적 인 frame 을 분석 하 는 것 입 니 다. SYN STREAM control frame 은 stream 을 만 들 고 요청 을 시작 합 니 다. 다음은 frame 의 처리 과정 을 살 펴 보 겠 습 니 다. 다른 frame 은 본 고 에서 분석 하지 않 습 니 다.
static ngx_int_t
ngx_http_spdy_process_syn_stream(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;

//                   ,   10     Stream-ID, Associated-To-Stream-ID      。
    if (size < 10) {
        return NGX_AGAIN;
    }

    p = *pos;

    sc->length -= 10;
    *pos += 10;

//   stream id stream    。
    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                     stream  。
    if (sc->processing == sscf->concurrent_streams) {
        ngx_http_spdy_send_rst_stream(sc, sid, NGX_SPDY_REFUSED_STREAM);

        sc->handler = ngx_http_spdy_skip_headers;
        return NGX_OK;
    }

//       http request  。
    r = ngx_http_spdy_create_request(sc);
    if (r == NULL) {
        return NGX_ERROR;
    }

//       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;

//     ,flags    FIN_FLAG    stream    data frame ,       http get  ,  body  。     stream   half close  。
    stream->half_closed = sc->flags & NGX_SPDY_FLAG_FIN;

。。。。。。。。。。。。。。。。。。。。。。。。

    sc->stream = stream;

//        headers 。
    sc->handler = ngx_http_spdy_process_headers;

    return NGX_OK;
}

지금부터 headers 를 분석 합 니 다. 구체 적 인 분석 과정 은 분석 하지 않 습 니 다. 관심 있 는 독 자 는 spdy 프로 토 콜 draft 문 서 를 대조 하여 코드 를 분석 할 수 있 습 니 다. ngx http spdy process headers 의 마지막 몇 줄 코드 를 보 겠 습 니 다.
static ngx_int_t
ngx_http_spdy_process_headers(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++;

//              ,         。
    ngx_http_spdy_run_request(r);

//   frame     ,   spdy    frame        ,       frame。
    sc->handler = ngx_http_spdy_process_frame;

    return NGX_DONE;
}

static void
ngx_http_spdy_run_request(ngx_http_request_t *r)
{
    ngx_uint_t                  i;
    ngx_list_part_t            *part;
    ngx_table_elt_t            *h;
    ngx_connection_t           *fc;
    ngx_http_header_t          *hh;
    ngx_http_core_main_conf_t  *cmcf;

//   spdy          http    ,        spdy 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;

//   header,     spdy 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");
        ngx_http_spdy_finalize_request(r, NGX_HTTP_TO_HTTPS);
        return;
    }

#if (NGX_STAT_STUB)
    (void) ngx_atomic_fetch_add(ngx_stat_reading, -1);
    r->stat_reading = 0;
    (void) ngx_atomic_fetch_add(ngx_stat_writing, 1);
    r->stat_writing = 1;
#endif

    r->write_event_handler = ngx_http_core_run_phases;

//       ,       phases  ,          http      。
//           handler,filter     。
    ngx_http_core_run_phases(r);
    ngx_http_run_posted_requests(fc);
}

요약:
spdy patch 의 실현 은 매우 뚜렷 합 니 다. 주요 목 표 는 두 가지 입 니 다. 1. spdy frame 을 해석 하 는 것 입 니 다. 2. http request 로 전환 하 는 것 입 니 다. 코드 를 통 해 알 수 있 듯 이 이 patch 는 초보적인 실현 일 뿐 이 고 완선 되 지 않 은 부분 이 많 습 니 다. 뒤의 nginx 홈 페이지 는 실현 에 있어 서 큰 조정 이 있 는 것 도 이상 하지 않 습 니 다. 물론 spdy 의 server push 등 복잡 하고 강력 한 기능 을 가지 고 있 습 니 다.이 패 치 에 서 는 전혀 실현 되 지 않 았 습 니 다.

좋은 웹페이지 즐겨찾기