Beispiel #1
0
CURLcode Curl_http2_switched(struct connectdata *conn,
                             const char *mem, size_t nread)
{
  CURLcode result;
  struct http_conn *httpc = &conn->proto.httpc;
  int rv;
  struct SessionHandle *data = conn->data;
  struct HTTP *stream = conn->data->req.protop;

  result = Curl_http2_setup(conn);
  if(result)
    return result;

  httpc->recv_underlying = (recving)conn->recv[FIRSTSOCKET];
  httpc->send_underlying = (sending)conn->send[FIRSTSOCKET];
  conn->recv[FIRSTSOCKET] = http2_recv;
  conn->send[FIRSTSOCKET] = http2_send;

  if(conn->data->req.upgr101 == UPGR101_RECEIVED) {
    /* stream 1 is opened implicitly on upgrade */
    stream->stream_id = 1;
    /* queue SETTINGS frame (again) */
    rv = nghttp2_session_upgrade(httpc->h2, httpc->binsettings,
                                 httpc->binlen, NULL);
    if(rv != 0) {
      failf(data, "nghttp2_session_upgrade() failed: %s(%d)",
            nghttp2_strerror(rv), rv);
      return CURLE_HTTP2;
    }
  }
  else {
    /* stream ID is unknown at this point */
    stream->stream_id = -1;
    rv = nghttp2_submit_settings(httpc->h2, NGHTTP2_FLAG_NONE, NULL, 0);
    if(rv != 0) {
      failf(data, "nghttp2_submit_settings() failed: %s(%d)",
            nghttp2_strerror(rv), rv);
      return CURLE_HTTP2;
    }
  }

  rv = (int)nghttp2_session_mem_recv(httpc->h2, (const uint8_t*)mem, nread);

  if(rv != (int)nread) {
    failf(data, "nghttp2_session_mem_recv() failed: %s(%d)",
          nghttp2_strerror(rv), rv);
    return CURLE_HTTP2;
  }

  /* Try to send some frames since we may read SETTINGS already. */
  rv = nghttp2_session_send(httpc->h2);

  if(rv != 0) {
    failf(data, "nghttp2_session_send() failed: %s(%d)",
          nghttp2_strerror(rv), rv);
    return CURLE_HTTP2;
  }

  return CURLE_OK;
}
Beispiel #2
0
CURLcode Curl_http2_switched(struct connectdata *conn)
{
  CURLcode rc;
  struct http_conn *httpc = &conn->proto.httpc;
  int rv;
  struct SessionHandle *data = conn->data;

  httpc->recv_underlying = (recving)conn->recv[FIRSTSOCKET];
  httpc->send_underlying = (sending)conn->send[FIRSTSOCKET];
  conn->recv[FIRSTSOCKET] = http2_recv;
  conn->send[FIRSTSOCKET] = http2_send;

  rv = (int) ((Curl_send*)httpc->send_underlying)
    (conn, FIRSTSOCKET,
     NGHTTP2_CLIENT_CONNECTION_PREFACE,
     NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN,
     &rc);
  if(rc)
    /* TODO: This may get CURLE_AGAIN */
    return rc;

  if(rv != 24) {
    failf(data, "Only sent partial HTTP2 packet");
    return CURLE_SEND_ERROR;
  }

  if(conn->data->req.upgr101 == UPGR101_RECEIVED) {
    /* stream 1 is opened implicitly on upgrade */
    httpc->stream_id = 1;
    /* queue SETTINGS frame (again) */
    rv = nghttp2_session_upgrade(httpc->h2, httpc->binsettings,
                                 httpc->binlen, NULL);
    if(rv != 0) {
      failf(data, "nghttp2_session_upgrade() failed: %s(%d)",
            nghttp2_strerror(rv), rv);
      return CURLE_HTTP2;
    }
  }
  else {
    /* stream ID is unknown at this point */
    httpc->stream_id = -1;
    rv = nghttp2_submit_settings(httpc->h2, NGHTTP2_FLAG_NONE, NULL, 0);
    if(rv != 0) {
      failf(data, "nghttp2_submit_settings() failed: %s(%d)",
            nghttp2_strerror(rv), rv);
      return CURLE_HTTP2;
    }
  }
  return CURLE_OK;
}
Beispiel #3
0
/* Start submitting the response to a stream request. This is possible
 * once we have all the response headers. The response body will be
 * read by the session using the callback we supply.
 */
apr_status_t h2_session_handle_response(h2_session *session, h2_stream *stream)
{
    apr_status_t status = APR_SUCCESS;
    int rv = 0;
    AP_DEBUG_ASSERT(session);
    AP_DEBUG_ASSERT(stream);
    AP_DEBUG_ASSERT(stream->response);
    
    if (stream->response->ngheader) {
        rv = submit_response(session, stream->response);
    }
    else {
        rv = nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE,
                                       stream->id, NGHTTP2_PROTOCOL_ERROR);
    }
    
    if (nghttp2_is_fatal(rv)) {
        status = APR_EGENERAL;
        h2_session_abort_int(session, rv);
        ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
                      APLOGNO(02940) "submit_response: %s", 
                      nghttp2_strerror(rv));
    }
    return status;
}
Beispiel #4
0
static int error_reply(nghttp2_session *session,
                       http2_stream_data *stream_data)
{
  int rv;
  int pipefd[2];
  nghttp2_nv hdrs[] = {
    MAKE_NV(":status", "404")
  };

  rv = pipe(pipefd);
  if(rv != 0) {
    warn("Could not create pipe");
    rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
                                   stream_data->stream_id,
                                   NGHTTP2_INTERNAL_ERROR);
    if(rv != 0) {
      warnx("Fatal error: %s", nghttp2_strerror(rv));
      return -1;
    }
    return 0;
  }
  write(pipefd[1], ERROR_HTML, sizeof(ERROR_HTML) - 1);
  close(pipefd[1]);
  stream_data->fd = pipefd[0];
  if(send_response(session, stream_data->stream_id, hdrs, ARRLEN(hdrs),
                   pipefd[0]) != 0) {
    close(pipefd[0]);
    return -1;
  }
  return 0;
}
Beispiel #5
0
apr_status_t h2_session_goaway(h2_session *session, apr_status_t reason)
{
    assert(session);
    apr_status_t status = APR_SUCCESS;
    if (session->aborted) {
        return APR_EINVAL;
    }
    
    int rv = 0;
    if (reason == APR_SUCCESS) {
        rv = nghttp2_submit_shutdown_notice(session->ngh2);
    }
    else {
        int err = 0;
        int last_id = nghttp2_session_get_last_proc_stream_id(session->ngh2);
        rv = nghttp2_submit_goaway(session->ngh2, last_id,
                                   NGHTTP2_FLAG_NONE, err, NULL, 0);
    }
    if (rv != 0) {
        status = APR_EGENERAL;
        ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
                      "session(%ld): submit goaway: %s",
                      session->id, nghttp2_strerror(rv));
    }
    return status;
}
Beispiel #6
0
static int submit_response(h2_session *session, h2_response *response)
{
    nghttp2_data_provider provider;
    int rv;
    
    memset(&provider, 0, sizeof(provider));
    provider.source.fd = response->stream_id;
    provider.read_callback = stream_data_cb;
    
    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
                  "h2_stream(%ld-%d): submitting response %s",
                  session->id, response->stream_id, response->status);
    
    rv = nghttp2_submit_response(session->ngh2, response->stream_id,
                                 response->ngheader->nv, 
                                 response->ngheader->nvlen, &provider);
    
    if (rv != 0) {
        ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
                      APLOGNO(02939) "h2_stream(%ld-%d): submit_response: %s",
                      session->id, response->stream_id, nghttp2_strerror(rv));
    }
    else {
        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
                      "h2_stream(%ld-%d): submitted response %s, rv=%d",
                      session->id, response->stream_id, 
                      response->status, rv);
    }
    return rv;
}
Beispiel #7
0
static apr_status_t init_callbacks(conn_rec *c, nghttp2_session_callbacks **pcb)
{
    int rv = nghttp2_session_callbacks_new(pcb);
    if (rv != 0) {
        ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c,
                      APLOGNO(02926) "nghttp2_session_callbacks_new: %s",
                      nghttp2_strerror(rv));
        return APR_EGENERAL;
    }
    
    NGH2_SET_CALLBACK(*pcb, send, send_cb);
    NGH2_SET_CALLBACK(*pcb, on_frame_recv, on_frame_recv_cb);
    NGH2_SET_CALLBACK(*pcb, on_invalid_frame_recv, on_invalid_frame_recv_cb);
    NGH2_SET_CALLBACK(*pcb, on_data_chunk_recv, on_data_chunk_recv_cb);
    NGH2_SET_CALLBACK(*pcb, before_frame_send, before_frame_send_cb);
    NGH2_SET_CALLBACK(*pcb, on_frame_send, on_frame_send_cb);
    NGH2_SET_CALLBACK(*pcb, on_frame_not_send, on_frame_not_send_cb);
    NGH2_SET_CALLBACK(*pcb, on_stream_close, on_stream_close_cb);
    NGH2_SET_CALLBACK(*pcb, on_begin_headers, on_begin_headers_cb);
    NGH2_SET_CALLBACK(*pcb, on_header, on_header_cb);
    NGH2_SET_CALLBACK(*pcb, send_data, on_send_data_cb);
    NGH2_SET_CALLBACK(*pcb, data_source_read_length, on_data_source_read_length_cb);
    
    return APR_SUCCESS;
}
Beispiel #8
0
/* Start submitting the response to a stream request. This is possible
 * once we have all the response headers. The response body will be
 * read by the session using the callback we supply.
 */
apr_status_t h2_session_handle_response(h2_session *session,
                                        h2_stream *stream, h2_response *head)
{
    assert(session);
    apr_status_t status = APR_SUCCESS;
    int rv = 0;
    if (head->http_status) {
        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
                      "h2_stream(%ld-%d): submitting response %s with %d headers",
                      session->id, stream->id, head->http_status,
                      (int)head->nvlen);
        assert(head->nvlen);
        
        nghttp2_data_provider provider = {
            stream->id, stream_data_cb
        };
        rv = nghttp2_submit_response(session->ngh2, stream->id,
                                     &head->nv, head->nvlen, &provider);
        if (rv != 0) {
            ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
                          "h2_stream(%ld-%d): submit_response: %s",
                          session->id, stream->id, nghttp2_strerror(rv));
        }
        else {
            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
                          "h2_stream(%ld-%d): submitted response %s with %d "
                          "headers, rv=%d",
                          session->id, stream->id, head->http_status,
                          (int)head->nvlen, rv);
        }
    }
    else {
        rv = nghttp2_submit_rst_stream(session->ngh2, 0,
                                       stream->id, NGHTTP2_ERR_INVALID_STATE);
    }
    
    if (nghttp2_is_fatal(rv)) {
        status = APR_EGENERAL;
        h2_session_abort_int(session, rv);
        ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
                      "submit_response: %s",
                      nghttp2_strerror(rv));
    }
    return status;
}
Beispiel #9
0
/* Serialize the frame and send (or buffer) the data to
   bufferevent. */
static int session_send(http2_session_data *session_data) {
  int rv;
  rv = nghttp2_session_send(session_data->session);
  if (rv != 0) {
    warnx("Fatal error: %s", nghttp2_strerror(rv));
    return -1;
  }
  return 0;
}
Beispiel #10
0
apr_status_t h2_session_write(h2_session *session, apr_interval_time_t timeout)
{
    apr_status_t status = APR_EAGAIN;
    h2_response *response = NULL;
    int have_written = 0;
    
    assert(session);
    
    /* Check that any pending window updates are sent. */
    status = h2_session_update_windows(session);
    if (status == APR_SUCCESS) {
        have_written = 1;
    }
    else if (status != APR_EAGAIN) {
        return status;
    }
    
    /* If we have responses ready, submit them now. */
    while ((response = h2_session_pop_response(session)) != NULL) {
        h2_stream *stream = h2_session_get_stream(session, response->stream_id);
        if (stream) {
            status = h2_session_handle_response(session, stream, response);
            have_written = 1;
        }
        h2_response_destroy(response);
        response = NULL;
    }
    h2_session_resume_streams_with_data(session);
    
    if (!have_written && timeout > 0 && !h2_session_want_write(session)) {
        status = h2_mplx_out_trywait(session->mplx, timeout, session->iowait);
    }
    h2_session_resume_streams_with_data(session);
    
    if (h2_session_want_write(session)) {
        status = APR_SUCCESS;
        int rv = nghttp2_session_send(session->ngh2);
        if (rv != 0) {
            ap_log_cerror( APLOG_MARK, APLOG_INFO, 0, session->c,
                          "h2_session: send: %s", nghttp2_strerror(rv));
            if (nghttp2_is_fatal(rv)) {
                h2_session_abort_int(session, rv);
                status = APR_ECONNABORTED;
            }
        }
        have_written = 1;
    }
    
    if (have_written) {
        h2_conn_io_flush(&session->io);
    }
    
    reap_zombies(session);

    return status;
}
Beispiel #11
0
static int on_invalid_frame_recv(nghttp2_session *session,
                                 const nghttp2_frame *frame,
                                 int lib_error_code, void *userp)
{
  struct connectdata *conn = (struct connectdata *)userp;
  (void)session;
  (void)frame;
  DEBUGF(infof(conn->data,
               "on_invalid_frame_recv() was called, error=%d:%s\n",
               lib_error_code, nghttp2_strerror(lib_error_code)));
  return 0;
}
/*
 * If the read would block (EWOULDBLOCK) we return -1. Otherwise we return
 * a regular CURLcode value.
 */
static ssize_t http2_recv(struct connectdata *conn, int sockindex,
                          char *mem, size_t len, CURLcode *err)
{
  CURLcode rc;
  ssize_t rv;
  ssize_t nread;
  char inbuf[H2_BUFSIZE];

  (void)sockindex; /* we always do HTTP2 on sockindex 0 */

  conn->proto.httpc.mem = mem;
  conn->proto.httpc.len = len;

  infof(conn->data, "http2_recv: %d bytes buffer\n",
        conn->proto.httpc.len);

  for(;;) {
    rc = Curl_read_plain(conn->sock[FIRSTSOCKET], inbuf, H2_BUFSIZE, &nread);

    if(rc == CURLE_AGAIN) {
      if(len == conn->proto.httpc.len) {
        *err = rc;
        return 0;
      }
      return len - conn->proto.httpc.len;
    }
    if(rc) {
      failf(conn->data, "Failed receiving HTTP2 data");
      *err = CURLE_RECV_ERROR;
      return 0;
    }

    if(!nread) {
      *err = CURLE_RECV_ERROR;
      return 0; /* TODO EOF? */
    }
    rv = nghttp2_session_mem_recv(conn->proto.httpc.h2,
                                  (const uint8_t *)inbuf, nread);

    if(nghttp2_is_fatal((int)rv)) {
      failf(conn->data, "nghttp2_session_mem_recv() returned %d:%s\n",
            rv, nghttp2_strerror((int)rv));
      *err = CURLE_RECV_ERROR;
      return 0;
    }
    if(rv < nread) {
      /* Happens when NGHTTP2_ERR_PAUSE is returned from user callback */
      break;
    }
    break;
  }
  return len - conn->proto.httpc.len;
}
Beispiel #13
0
static void send_client_connection_header(http2_session_data *session_data) {
  nghttp2_settings_entry iv[1] = {
      {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}};
  int rv;

  /* client 24 bytes magic string will be sent by nghttp2 library */
  rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, iv,
                               ARRLEN(iv));
  if (rv != 0) {
    errx(1, "Could not submit SETTINGS: %s", nghttp2_strerror(rv));
  }
}
Beispiel #14
0
static
void newconn(struct evconnlistener *lev, evutil_socket_t sock, struct sockaddr *cli, int socklen, void *raw)
{
    server *serv = raw;
    session *sess;
    printf("New client\n");
    sess = calloc(1, sizeof(*sess));
    if(sess) {
        sess->serv = serv;
        sess->S.build_stream = buildstream;
        sess->S.cleanup = &cleanup_session;
        /* periodic timer */
        sess->pingtimer = event_new(serv->base, -1, EV_PERSIST, pingconn, sess);
        assert(sess->pingtimer);
        sess->S.bev = bufferevent_socket_new(serv->base, sock, BEV_OPT_CLOSE_ON_FREE);
        if(sess->S.bev) {
            h2session_setup_bev(&sess->S);
            bufferevent_enable(sess->S.bev, EV_READ);

            if(prepare_h2_session(sess)) {
                bufferevent_free(sess->S.bev);
                free(sess);
                printf("Client failed\n");
                return;

            } else {
                nghttp2_settings_entry iv[] = {
                    {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100},
                    {NGHTTP2_SETTINGS_ENABLE_PUSH, 0}
                };
                int rv;

                if ((rv=nghttp2_submit_settings(sess->S.h2sess, NGHTTP2_FLAG_NONE, iv, ARRLEN(iv))) ||
                        (rv=nghttp2_session_send(sess->S.h2sess)))
                {
                    printf("submit error: %s", nghttp2_strerror(rv));
                    cleanup_session(&sess->S);
                } else {
                    const struct timeval itvl = {5,0};
                    printf("Connection ready\n");
                    evtimer_add(sess->pingtimer, &itvl);
                }
            }
        }
    }
    if(!sess || !sess->S.bev) {
        fprintf(stderr, "No memory\n");
        free(sess);
        close(sock);
        return;
    }
}
Beispiel #15
0
/* Send HTTP/2 client connection header, which includes 24 bytes
   magic octets and SETTINGS frame */
static int send_server_connection_header(http2_session_data *session_data) {
  nghttp2_settings_entry iv[1] = {
      {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}};
  int rv;

  rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, iv,
                               ARRLEN(iv));
  if (rv != 0) {
    warnx("Fatal error: %s", nghttp2_strerror(rv));
    return -1;
  }
  return 0;
}
Beispiel #16
0
static int send_response(nghttp2_session *session, int32_t stream_id,
                         nghttp2_nv *nva, size_t nvlen, int fd) {
  int rv;
  nghttp2_data_provider data_prd;
  data_prd.source.fd = fd;
  data_prd.read_callback = file_read_callback;

  rv = nghttp2_submit_response(session, stream_id, nva, nvlen, &data_prd);
  if (rv != 0) {
    warnx("Fatal error: %s", nghttp2_strerror(rv));
    return -1;
  }
  return 0;
}
Beispiel #17
0
static int on_invalid_frame_recv(nghttp2_session *session,
                                 const nghttp2_frame *frame,
                                 int lib_error_code, void *userp)
{
  struct SessionHandle *data_s = NULL;
  (void)userp;

  data_s = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
  if(data_s) {
    DEBUGF(infof(data_s,
                 "on_invalid_frame_recv() was called, error=%d:%s\n",
                 lib_error_code, nghttp2_strerror(lib_error_code)));
  }
  return 0;
}
Beispiel #18
0
static void send_client_connection_header(http2_session_data *session_data)
{
  nghttp2_settings_entry iv[1] = {
    { NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100 }
  };
  int rv;

  bufferevent_write(session_data->bev,
                    NGHTTP2_CLIENT_CONNECTION_PREFACE,
                    NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN);
  rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE,
                               iv, ARRLEN(iv));
  if(rv != 0) {
    errx(1, "Could not submit SETTINGS: %s", nghttp2_strerror(rv));
  }
}
Beispiel #19
0
/* Read the data in the bufferevent and feed them into nghttp2 library
   function. Invocation of nghttp2_session_mem_recv() may make
   additional pending frames, so call session_send() at the end of the
   function. */
static int session_recv(http2_session_data *session_data)
{
  int rv;
  struct evbuffer *input = bufferevent_get_input(session_data->bev);
  size_t datalen = evbuffer_get_length(input);
  unsigned char *data = evbuffer_pullup(input, -1);
  rv = nghttp2_session_mem_recv(session_data->session, data, datalen);
  if(rv < 0) {
    warnx("Fatal error: %s", nghttp2_strerror(rv));
    return -1;
  }
  evbuffer_drain(input, rv);
  if(session_send(session_data) != 0) {
    return -1;
  }
  return 0;
}
Beispiel #20
0
static apr_status_t h2_session_abort_int(h2_session *session, int reason)
{
    AP_DEBUG_ASSERT(session);
    if (!session->aborted) {
        session->aborted = 1;
        if (session->ngh2) {
            
            if (!reason) {
                nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE, 
                                      session->max_stream_received, 
                                      reason, NULL, 0);
                nghttp2_session_send(session->ngh2);
                h2_conn_io_flush(&session->io);
            }
            else {
                const char *err = nghttp2_strerror(reason);
                
                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
                              "session(%ld): aborting session, reason=%d %s",
                              session->id, reason, err);
                
                if (NGHTTP2_ERR_EOF == reason) {
                    /* This is our way of indication that the connection is
                     * gone. No use to send any GOAWAY frames. */
                    nghttp2_session_terminate_session(session->ngh2, reason);
                }
                else {
                    /* The connection might still be there and we shut down
                     * with GOAWAY and reason information. */
                     nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE, 
                                           session->max_stream_received, 
                                           reason, (const uint8_t *)err, 
                                           strlen(err));
                     nghttp2_session_send(session->ngh2);
                     h2_conn_io_flush(&session->io);
                }
            }
        }
        h2_mplx_abort(session->mplx);
    }
    return APR_SUCCESS;
}
Beispiel #21
0
/* Read the data in the bufferevent and feed them into nghttp2 library
   function. Invocation of nghttp2_session_mem_recv() may make
   additional pending frames, so call session_send() at the end of the
   function. */
static int session_recv(http2_session_data *session_data) {
  ssize_t readlen;
  struct evbuffer *input = bufferevent_get_input(session_data->bev);
  size_t datalen = evbuffer_get_length(input);
  unsigned char *data = evbuffer_pullup(input, -1);

  readlen = nghttp2_session_mem_recv(session_data->session, data, datalen);
  if (readlen < 0) {
    warnx("Fatal error: %s", nghttp2_strerror((int)readlen));
    return -1;
  }
  if (evbuffer_drain(input, (size_t)readlen) != 0) {
    warnx("Fatal error: evbuffer_drain failed");
    return -1;
  }
  if (session_send(session_data) != 0) {
    return -1;
  }
  return 0;
}
Beispiel #22
0
static h2_stream *resume_on_data(void *ctx, h2_stream *stream) {
    h2_session *session = (h2_session *)ctx;
    assert(session);
    assert(stream);
    
    if (h2_stream_is_suspended(stream)) {
        ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, stream->pool,
                      "h2_stream(%ld-%d): suspended, checking for DATA",
                      h2_mplx_get_id(stream->m), stream->id);
        if (h2_mplx_out_has_data_for(stream->m, h2_stream_get_id(stream))) {
            h2_stream_set_suspended(stream, 0);
            int rv = nghttp2_session_resume_data(session->ngh2,
                                                 h2_stream_get_id(stream));
            ap_log_cerror(APLOG_MARK, nghttp2_is_fatal(rv)?
                          APLOG_ERR : APLOG_DEBUG, 0, session->c,
                          "h2_stream(%ld-%d): resuming stream %s",
                          session->id, stream->id, nghttp2_strerror(rv));
        }
    }
    return NULL;
}
Beispiel #23
0
/* Send HTTP request to the remote peer */
static void submit_request(http2_session_data *session_data) {
  int32_t stream_id;
  http2_stream_data *stream_data = session_data->stream_data;
  const char *uri = stream_data->uri;
  const struct http_parser_url *u = stream_data->u;
  nghttp2_nv hdrs[] = {
      MAKE_NV2(":method", "GET"),
      MAKE_NV(":scheme", &uri[u->field_data[UF_SCHEMA].off],
              u->field_data[UF_SCHEMA].len),
      MAKE_NV(":authority", stream_data->authority, stream_data->authoritylen),
      MAKE_NV(":path", stream_data->path, stream_data->pathlen)};
  fprintf(stderr, "Request headers:\n");
  print_headers(stderr, hdrs, ARRLEN(hdrs));
  stream_id = nghttp2_submit_request(session_data->session, NULL, hdrs,
                                     ARRLEN(hdrs), NULL, stream_data);
  if (stream_id < 0) {
    errx(1, "Could not submit HTTP request: %s", nghttp2_strerror(stream_id));
  }

  stream_data->stream_id = stream_id;
}
Beispiel #24
0
static apr_status_t stream_destroy(h2_session *session, 
                                   h2_stream *stream,
                                   uint32_t error_code) 
{
    if (!error_code) {
        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
                      "h2_stream(%ld-%d): handled, closing", 
                      session->id, (int)stream->id);
        if (stream->id > session->max_stream_handled) {
            session->max_stream_handled = stream->id;
        }
    }
    else {
        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
                      "h2_stream(%ld-%d): closing with err=%d %s", 
                      session->id, (int)stream->id, (int)error_code,
                      nghttp2_strerror(error_code));
    }
    
    h2_stream_set_remove(session->streams, stream);
    return h2_mplx_cleanup_stream(session->mplx, stream);
}
Beispiel #25
0
static int resume_on_data(void *ctx, h2_stream *stream) {
    resume_ctx *rctx = (resume_ctx*)ctx;
    h2_session *session = rctx->session;
    AP_DEBUG_ASSERT(session);
    AP_DEBUG_ASSERT(stream);
    
    if (h2_stream_is_suspended(stream)) {
        if (h2_mplx_out_has_data_for(stream->m, stream->id)) {
            int rv;
            h2_stream_set_suspended(stream, 0);
            ++rctx->resume_count;
            
            rv = nghttp2_session_resume_data(session->ngh2, stream->id);
            ap_log_cerror(APLOG_MARK, nghttp2_is_fatal(rv)?
                          APLOG_ERR : APLOG_DEBUG, 0, session->c,
                          APLOGNO(02936) 
                          "h2_stream(%ld-%d): resuming stream %s",
                          session->id, stream->id, nghttp2_strerror(rv));
        }
    }
    return 1;
}
Beispiel #26
0
/* readcb for bufferevent. Here we get the data from the input buffer
   of bufferevent and feed them to nghttp2 library. This may invoke
   nghttp2 callbacks. It may also queues the frame in nghttp2 session
   context. To send them, we call session_send() in the end. */
static void readcb(struct bufferevent *bev, void *ptr) {
  http2_session_data *session_data = (http2_session_data *)ptr;
  ssize_t readlen;
  struct evbuffer *input = bufferevent_get_input(bev);
  size_t datalen = evbuffer_get_length(input);
  unsigned char *data = evbuffer_pullup(input, -1);

  readlen = nghttp2_session_mem_recv(session_data->session, data, datalen);
  if (readlen < 0) {
    warnx("Fatal error: %s", nghttp2_strerror((int)readlen));
    delete_http2_session_data(session_data);
    return;
  }
  if (evbuffer_drain(input, (size_t)readlen) != 0) {
    warnx("Fatal error: evbuffer_drain failed");
    delete_http2_session_data(session_data);
    return;
  }
  if (session_send(session_data) != 0) {
    delete_http2_session_data(session_data);
    return;
  }
}
Beispiel #27
0
/*
 * If the read would block (EWOULDBLOCK) we return -1. Otherwise we return
 * a regular CURLcode value.
 */
static ssize_t http2_recv(struct connectdata *conn, int sockindex,
                          char *mem, size_t len, CURLcode *err)
{
  CURLcode rc;
  ssize_t rv;
  ssize_t nread;
  struct http_conn *httpc = &conn->proto.httpc;

  (void)sockindex; /* we always do HTTP2 on sockindex 0 */

  if(httpc->closed) {
    /* Reset to FALSE to prevent infinite loop in readwrite_data
       function. */
    httpc->closed = FALSE;
    return 0;
  }

  /* Nullify here because we call nghttp2_session_send() and they
     might refer to the old buffer. */
  httpc->upload_mem = NULL;
  httpc->upload_len = 0;

  if(httpc->bodystarted &&
     httpc->nread_header_recvbuf < httpc->header_recvbuf->size_used) {
    size_t left =
      httpc->header_recvbuf->size_used - httpc->nread_header_recvbuf;
    size_t ncopy = len < left ? len : left;
    memcpy(mem, httpc->header_recvbuf->buffer + httpc->nread_header_recvbuf,
           ncopy);
    httpc->nread_header_recvbuf += ncopy;
    return ncopy;
  }

  if(httpc->data) {
    nread = len < httpc->datalen ? len : httpc->datalen;
    memcpy(mem, httpc->data, nread);

    httpc->data += nread;
    httpc->datalen -= nread;

    infof(conn->data, "%zu data written\n", nread);
    if(httpc->datalen == 0) {
      httpc->data = NULL;
      httpc->datalen = 0;
    }
    return nread;
  }

  conn->proto.httpc.mem = mem;
  conn->proto.httpc.len = len;

  infof(conn->data, "http2_recv: %d bytes buffer\n",
        conn->proto.httpc.len);

  rc = 0;
  nread = ((Curl_recv*)httpc->recv_underlying)(conn, FIRSTSOCKET,
                                               httpc->inbuf, H2_BUFSIZE, &rc);

  if(rc == CURLE_AGAIN) {
    *err = rc;
    return -1;
  }

  if(nread == -1) {
    failf(conn->data, "Failed receiving HTTP2 data");
    *err = rc;
    return 0;
  }

  infof(conn->data, "nread=%zd\n", nread);
  rv = nghttp2_session_mem_recv(httpc->h2,
                                (const uint8_t *)httpc->inbuf, nread);

  if(nghttp2_is_fatal((int)rv)) {
    failf(conn->data, "nghttp2_session_mem_recv() returned %d:%s\n",
          rv, nghttp2_strerror((int)rv));
    *err = CURLE_RECV_ERROR;
    return 0;
  }
  infof(conn->data, "nghttp2_session_mem_recv() returns %zd\n", rv);
  /* Always send pending frames in nghttp2 session, because
     nghttp2_session_mem_recv() may queue new frame */
  rv = nghttp2_session_send(httpc->h2);
  if(rv != 0) {
    *err = CURLE_SEND_ERROR;
    return 0;
  }
  if(len != httpc->len) {
    return len - conn->proto.httpc.len;
  }
  /* If stream is closed, return 0 to signal the http routine to close
     the connection */
  if(httpc->closed) {
    /* Reset to FALSE to prevent infinite loop in readwrite_data
       function. */
    httpc->closed = FALSE;
    return 0;
  }
  *err = CURLE_AGAIN;
  return -1;
}
Beispiel #28
0
int Curl_http2_switched(struct connectdata *conn)
{
  int rv;
  CURLcode rc;
  struct http_conn *httpc = &conn->proto.httpc;
  /* we are switched! */
  /* Don't know this is needed here at this moment. Original
     handler->flags is still useful. */
  if(conn->handler->flags & PROTOPT_SSL)
    conn->handler = &Curl_handler_http2_ssl;
  else
    conn->handler = &Curl_handler_http2;

  httpc->recv_underlying = (recving)conn->recv[FIRSTSOCKET];
  httpc->send_underlying = (sending)conn->send[FIRSTSOCKET];
  conn->recv[FIRSTSOCKET] = http2_recv;
  conn->send[FIRSTSOCKET] = http2_send;
  infof(conn->data, "We have switched to HTTP2\n");
  httpc->bodystarted = FALSE;
  httpc->closed = FALSE;
  httpc->header_recvbuf = Curl_add_buffer_init();
  httpc->nread_header_recvbuf = 0;
  httpc->data = NULL;
  httpc->datalen = 0;
  httpc->upload_left = 0;
  httpc->upload_mem = NULL;
  httpc->upload_len = 0;

  conn->httpversion = 20;

  /* Put place holder for status line */
  Curl_add_buffer(httpc->header_recvbuf, "HTTP/2.0 200\r\n", 14);

  /* TODO: May get CURLE_AGAIN */
  rv = (int) ((Curl_send*)httpc->send_underlying)
    (conn, FIRSTSOCKET,
     NGHTTP2_CLIENT_CONNECTION_HEADER,
     NGHTTP2_CLIENT_CONNECTION_HEADER_LEN,
     &rc);
  assert(rv == 24);
  if(conn->data->req.upgr101 == UPGR101_RECEIVED) {
    /* stream 1 is opened implicitly on upgrade */
    httpc->stream_id = 1;
    /* queue SETTINGS frame (again) */
    rv = nghttp2_session_upgrade(httpc->h2, httpc->binsettings,
                                 httpc->binlen, NULL);
    if(rv != 0) {
      failf(conn->data, "nghttp2_session_upgrade() failed: %s(%d)",
            nghttp2_strerror(rv), rv);
      return -1;
    }
  }
  else {
    /* stream ID is unknown at this point */
    httpc->stream_id = -1;
    rv = nghttp2_submit_settings(httpc->h2, NGHTTP2_FLAG_NONE, NULL, 0);
    if(rv != 0) {
      failf(conn->data, "nghttp2_submit_settings() failed: %s(%d)",
            nghttp2_strerror(rv), rv);
      return -1;
    }
  }
  return 0;
}
Beispiel #29
0
/*
 * If the read would block (EWOULDBLOCK) we return -1. Otherwise we return
 * a regular CURLcode value.
 */
static ssize_t http2_recv(struct connectdata *conn, int sockindex,
                          char *mem, size_t len, CURLcode *err)
{
  CURLcode result = CURLE_OK;
  ssize_t rv;
  ssize_t nread;
  struct http_conn *httpc = &conn->proto.httpc;
  struct SessionHandle *data = conn->data;
  struct HTTP *stream = data->req.protop;

  (void)sockindex; /* we always do HTTP2 on sockindex 0 */

  /* If stream is closed, return 0 to signal the http routine to close
     the connection.  We need to handle stream closure here,
     otherwise, we may be going to read from underlying connection,
     and gets EAGAIN, and we will get stuck there. */
  if(stream->memlen == 0 && stream->closed) {
    return http2_handle_stream_close(httpc, data, stream, err);
  }

  /* Nullify here because we call nghttp2_session_send() and they
     might refer to the old buffer. */
  stream->upload_mem = NULL;
  stream->upload_len = 0;

  /*
   * At this point 'stream' is just in the SessionHandle the connection
   * identifies as its owner at this time.
   */

  if(stream->bodystarted &&
     stream->nread_header_recvbuf < stream->header_recvbuf->size_used) {
    /* If there is body data pending for this stream to return, do that */
    size_t left =
      stream->header_recvbuf->size_used - stream->nread_header_recvbuf;
    size_t ncopy = MIN(len, left);
    memcpy(mem, stream->header_recvbuf->buffer + stream->nread_header_recvbuf,
           ncopy);
    stream->nread_header_recvbuf += ncopy;

    infof(data, "http2_recv: Got %d bytes from header_recvbuf\n",
          (int)ncopy);
    return ncopy;
  }

  infof(data, "http2_recv: %d bytes buffer at %p (stream %u)\n",
        len, mem, stream->stream_id);

  if((data->state.drain) && stream->memlen) {
    DEBUGF(infof(data, "http2_recv: DRAIN %zu bytes stream %u!! (%p => %p)\n",
                 stream->memlen, stream->stream_id,
                 stream->mem, mem));
    if(mem != stream->mem) {
      /* if we didn't get the same buffer this time, we must move the data to
         the beginning */
      memmove(mem, stream->mem, stream->memlen);
      stream->len = len - stream->memlen;
      stream->mem = mem;
    }
  }
  else if(stream->pausedata) {
    nread = MIN(len, stream->pauselen);
    memcpy(mem, stream->pausedata, nread);

    stream->pausedata += nread;
    stream->pauselen -= nread;

    infof(data, "%zu data bytes written\n", nread);
    if(stream->pauselen == 0) {
      DEBUGF(infof(data, "Unpaused by stream %u\n", stream->stream_id));
      assert(httpc->pause_stream_id == stream->stream_id);
      httpc->pause_stream_id = 0;

      stream->pausedata = NULL;
      stream->pauselen = 0;
    }
    infof(data, "http2_recv: returns unpaused %zd bytes on stream %u\n",
          nread, stream->stream_id);
    return nread;
  }
  else if(httpc->pause_stream_id) {
    /* If a stream paused nghttp2_session_mem_recv previously, and has
       not processed all data, it still refers to the buffer in
       nghttp2_session.  If we call nghttp2_session_mem_recv(), we may
       overwrite that buffer.  To avoid that situation, just return
       here with CURLE_AGAIN.  This could be busy loop since data in
       socket is not read.  But it seems that usually streams are
       notified with its drain property, and socket is read again
       quickly. */
    *err = CURLE_AGAIN;
    return -1;
  }
  else {
    char *inbuf;
    /* remember where to store incoming data for this stream and how big the
       buffer is */
    stream->mem = mem;
    stream->len = len;
    stream->memlen = 0;

    if(httpc->inbuflen == 0) {
      nread = ((Curl_recv *)httpc->recv_underlying)(
          conn, FIRSTSOCKET, httpc->inbuf, H2_BUFSIZE, &result);

      if(result == CURLE_AGAIN) {
        *err = result;
        return -1;
      }

      if(nread == -1) {
        failf(data, "Failed receiving HTTP2 data");
        *err = result;
        return 0;
      }

      if(nread == 0) {
        failf(data, "Unexpected EOF");
        *err = CURLE_RECV_ERROR;
        return -1;
      }

      DEBUGF(infof(data, "nread=%zd\n", nread));

      httpc->inbuflen = nread;
      inbuf = httpc->inbuf;
    }
    else {
      nread = httpc->inbuflen - httpc->nread_inbuf;
      inbuf = httpc->inbuf + httpc->nread_inbuf;

      DEBUGF(infof(data, "Use data left in connection buffer, nread=%zd\n",
                   nread));
    }
    rv = nghttp2_session_mem_recv(httpc->h2, (const uint8_t *)inbuf, nread);

    if(nghttp2_is_fatal((int)rv)) {
      failf(data, "nghttp2_session_mem_recv() returned %d:%s\n",
            rv, nghttp2_strerror((int)rv));
      *err = CURLE_RECV_ERROR;
      return 0;
    }
    DEBUGF(infof(data, "nghttp2_session_mem_recv() returns %zd\n", rv));
    if(nread == rv) {
      DEBUGF(infof(data, "All data in connection buffer processed\n"));
      httpc->inbuflen = 0;
      httpc->nread_inbuf = 0;
    }
    else {
      httpc->nread_inbuf += rv;
      DEBUGF(infof(data, "%zu bytes left in connection buffer\n",
                   httpc->inbuflen - httpc->nread_inbuf));
    }
    /* Always send pending frames in nghttp2 session, because
       nghttp2_session_mem_recv() may queue new frame */
    rv = nghttp2_session_send(httpc->h2);
    if(rv != 0) {
      *err = CURLE_SEND_ERROR;
      return 0;
    }
  }
  if(stream->memlen) {
    ssize_t retlen = stream->memlen;
    infof(data, "http2_recv: returns %zd for stream %u\n",
          retlen, stream->stream_id);
    stream->memlen = 0;

    if(httpc->pause_stream_id == stream->stream_id) {
      /* data for this stream is returned now, but this stream caused a pause
         already so we need it called again asap */
      DEBUGF(infof(data, "Data returned for PAUSED stream %u\n",
                   stream->stream_id));
    }
    else
      data->state.drain = 0; /* this stream is hereby drained */

    return retlen;
  }
  /* If stream is closed, return 0 to signal the http routine to close
     the connection */
  if(stream->closed) {
    return http2_handle_stream_close(httpc, data, stream, err);
  }
  *err = CURLE_AGAIN;
  DEBUGF(infof(data, "http2_recv returns AGAIN for stream %u\n",
               stream->stream_id));
  return -1;
}
Beispiel #30
0
apr_status_t h2_session_write(h2_session *session, apr_interval_time_t timeout)
{
    apr_status_t status = APR_EAGAIN;
    h2_stream *stream = NULL;
    int flush_output = 0;
    
    AP_DEBUG_ASSERT(session);
    
    /* Check that any pending window updates are sent. */
    status = h2_session_update_windows(session);
    if (status == APR_SUCCESS) {
        flush_output = 1;
    }
    else if (status != APR_EAGAIN) {
        return status;
    }
    
    if (h2_session_want_write(session)) {
        int rv;
        status = APR_SUCCESS;
        rv = nghttp2_session_send(session->ngh2);
        if (rv != 0) {
            ap_log_cerror( APLOG_MARK, APLOG_DEBUG, 0, session->c,
                          "h2_session: send: %s", nghttp2_strerror(rv));
            if (nghttp2_is_fatal(rv)) {
                h2_session_abort_int(session, rv);
                status = APR_ECONNABORTED;
            }
        }
        flush_output = 1;
    }
    
    /* If we have responses ready, submit them now. */
    while ((stream = h2_mplx_next_submit(session->mplx, 
                                         session->streams)) != NULL) {
        status = h2_session_handle_response(session, stream);
        flush_output = 1;
    }
    
    if (h2_session_resume_streams_with_data(session) > 0) {
        flush_output = 1;
    }
    
    if (!flush_output && timeout > 0 && !h2_session_want_write(session)) {
        status = h2_mplx_out_trywait(session->mplx, timeout, session->iowait);

        if (status != APR_TIMEUP
            && h2_session_resume_streams_with_data(session) > 0) {
            flush_output = 1;
        }
        else {
            /* nothing happened to ongoing streams, do some house-keeping */
        }
    }
    
    if (h2_session_want_write(session)) {
        int rv;
        status = APR_SUCCESS;
        rv = nghttp2_session_send(session->ngh2);
        if (rv != 0) {
            ap_log_cerror( APLOG_MARK, APLOG_DEBUG, 0, session->c,
                          "h2_session: send2: %s", nghttp2_strerror(rv));
            if (nghttp2_is_fatal(rv)) {
                h2_session_abort_int(session, rv);
                status = APR_ECONNABORTED;
            }
        }
        flush_output = 1;
    }
    
    if (flush_output) {
        h2_conn_io_flush(&session->io);
    }
    
    return status;
}