I've attached a HTTP/2 multi example that I believe exhibits the same issue 
reported.

This is a best effort and I've taken some artistic license in modifying the 
code to emulate the behaviour of our real code. There may be errors, introduced 
by me or in the real code; either way it will be helpful to get feedback on any 
mistakes.

The application takes a single URL and requests it multiple times. The first 
request is performed in isolation (in order to perform the h2c upgrade[1]) and 
subsequent requests are all added to the multiple handle at the same time.

This was built with curl (github master at c068284882) and nghttp2 (0.6.6). The 
application was tested between two VM's with the server being nghttpx 
(0.6.7-DEV) instance acting as reverse proxy to a local apache instance. I have 
also attached the packet trace. 

Lucas

[1] If I add all easy handles at the same time, the request on stream 3 (first 
after upgrade) sometimes still contains the Upgrade header resulting in a 
server GOAWAY as reported by Sam and fixed by Tatsuhiro. Seperating out the 
requests leads to a more stable upgrade and continuation of requests on the 
same h2c connection.
/***************************************************************************
 *                                  _   _ ____  _
 *  Project                     ___| | | |  _ \| |
 *                             / __| | | | |_) | |
 *                            | (__| |_| |  _ <| |___
 *                             \___|\___/|_| \_\_____|
 *
 * Copyright (C) 1998 - 2011, Daniel Stenberg, <[email protected]>, et al.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution. The terms
 * are also available at http://curl.haxx.se/docs/copyright.html.
 *
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
 * copies of the Software, and permit persons to whom the Software is
 * furnished to do so, under the terms of the COPYING file.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ***************************************************************************/
/* This is an example application source code using multi interface & http/2 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* somewhat unix-specific */
#include <sys/time.h>
#include <unistd.h>

/* curl stuff */
#include <curl/curl.h>
#define MIN_REQ_CURL_VER 0x072100 /* 7.33.0, for HTTP2 support */

/* Number of requests to make */
#define NUM_REQUESTS 5

typedef struct {
    char*   msg;
    char*   source_uri;
    int     len;
} CurlResponse;

static size_t
curl_get_chunks(void *ptr, size_t size, size_t nmemb, CurlResponse *resp)
{
  size_t new_len = resp->len + size*nmemb;
    printf("Received curl chunk for URI %s of size %d, new total size %d\n",
            resp->source_uri, (int) (size*nmemb), (int) new_len);

  resp->msg = realloc(resp->msg, (new_len + 1) * sizeof(char));
  if (resp->msg == NULL) {
    fprintf(stderr, "Realloc for cURL response message failed!\n");
    return 0;
  }
    memcpy(resp->msg+resp->len, ptr, size*nmemb);
    resp->len = new_len;
  return size*nmemb;
}

/*
 * Download the same HTTP file multiple times!
 */

#define HANDLECOUNT 5 /* Numner of http requests to make */
int main(int argc, char* argv[])
{
  CURL *handles[HANDLECOUNT];
  CurlResponse resps[HANDLECOUNT];
  CURLM *multi_handle;
  curl_version_info_data* curlver;

  int still_running; /* keep number of running handles */
  int i;
  int rc_ok = 0;
    

  if(argc != 2) {
    fprintf(stderr, "[%s]: USAGE: %s <url1>\n", argv[0], argv[0]);
    return -1;
  }

  curlver = curl_version_info(CURLVERSION_NOW);
  if(curlver->version_num < MIN_REQ_CURL_VER) {
    fprintf(stderr, "Version of libcURL loaded is %s it is too old!", 
curlver->version);
  }

  /* init a multi stack */
  multi_handle = curl_multi_init();

  curl_multi_setopt(multi_handle, CURLMOPT_MAX_HOST_CONNECTIONS, 1);
  curl_multi_setopt(multi_handle, CURLMOPT_PIPELINING, 1);


  for (i=0; i<HANDLECOUNT; i++)
  {
    handles[i] = curl_easy_init();
    resps[i].msg  = NULL;
    resps[i].len = 0;
    resps[i].source_uri = argv[1];
    /* set options */
    curl_easy_setopt(handles[i], CURLOPT_URL, resps[i].source_uri);
    curl_easy_setopt(handles[i], CURLOPT_WRITEFUNCTION, curl_get_chunks);
    curl_easy_setopt(handles[i], CURLOPT_WRITEDATA, &resps[i]);
    if(curlver->features && CURL_VERSION_HTTP2)
      curl_easy_setopt(handles[i], CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
  }

  struct timeval timeout;
  int rc; /* select() return code */

  fd_set fdread;
  fd_set fdwrite;
  fd_set fdexcep;
  int maxfd = -1;

  long curl_timeo = -1;

  FD_ZERO(&fdread);
  FD_ZERO(&fdwrite);
  FD_ZERO(&fdexcep);

  /* set a suitable timeout to play around with */
  timeout.tv_sec = 1;
  timeout.tv_usec = 0;

  curl_multi_timeout(multi_handle, &curl_timeo);
  if(curl_timeo >= 0) 
  {
  timeout.tv_sec = curl_timeo / 1000;
  if(timeout.tv_sec > 1)
    timeout.tv_sec = 1;
  else
    timeout.tv_usec = (curl_timeo % 1000) * 1000;
  }

  // get file descriptors from the transfers
  curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);

  /* In a real-world program you OF COURSE check the return code of the
  function calls.  On success, the value of maxfd is guaranteed to be
  greater or equal than -1.  We call select(maxfd + 1, ...), specially in
  case of (maxfd == -1), we call select(0, ...), which is basically equal
  to sleep. */

  rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);

  switch(rc) {
  case -1:
  /* select error */
  break;
  case 0:
  default:
  /* timeout or readable/writable sockets */
  //curl_multi_perform(multi_handle, &still_running);
    rc_ok = 1;
  break;
  }
  
  if (rc_ok != 0)
  {
        /* Make a initial request in order to perform h2c upgrade */
    curl_multi_add_handle(multi_handle, handles[0]);
    curl_multi_perform(multi_handle, &still_running);
  }
  else return -1;


  for(i=1; i<NUM_REQUESTS; i++) 
  {
    curl_multi_add_handle(multi_handle, handles[i]);
  }

  do
  {
     curl_multi_perform(multi_handle, &still_running);
  } while (still_running);
  
  int exit =0;
  int k = 0;
  CURLMsg *curl_message;
  
  while (exit == 0)
  {
    curl_message = curl_multi_info_read(multi_handle, &k);
    if(curl_message == NULL) 
    {
      exit = 1;
    }
    else if(curl_message->msg == CURLMSG_DONE) 
    {
      if (curl_message->easy_handle == NULL) 
      {
        fprintf(stderr, "easy_handle NULL but should not have been\n"); /* 
observed this sometimes*/
        break;
      }
      else 
      {  
        fprintf(stderr, "Response received\n");
        curl_multi_remove_handle(multi_handle, curl_message->easy_handle);
      }
    }
    else
    {
      fprintf(stderr, "Other msg returned %u\n", curl_message->msg);
    }
  
  }

  curl_multi_cleanup(multi_handle);

  /* Free the CURL handles */
  for (i=0; i<HANDLECOUNT; i++)
      curl_easy_cleanup(handles[i]);

  return 0;
}

Attachment: curl_multi-http2.pcapng.gz
Description: curl_multi-http2.pcapng.gz

-------------------------------------------------------------------
List admin: http://cool.haxx.se/list/listinfo/curl-library
Etiquette:  http://curl.haxx.se/mail/etiquette.html

Reply via email to