|
|
|
@ -0,0 +1,604 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<!--[if IE 8]><html class="no-js lt-ie9" lang="en" > <![endif]-->
|
|
|
|
|
<!--[if gt IE 8]><!--> <html class="no-js" lang="en" > <!--<![endif]-->
|
|
|
|
|
<head>
|
|
|
|
|
<meta charset="utf-8">
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
|
|
|
|
|
|
<title>Tutorial: HTTP/2.0 client — nghttp2 0.3.0-DEV documentation</title>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<link href='https://fonts.googleapis.com/css?family=Lato:400,700|Roboto+Slab:400,700|Inconsolata:400,700' rel='stylesheet' type='text/css'>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<script type="text/javascript">
|
|
|
|
|
var DOCUMENTATION_OPTIONS = {
|
|
|
|
|
URL_ROOT:'',
|
|
|
|
|
VERSION:'0.3.0-DEV',
|
|
|
|
|
COLLAPSE_INDEX:false,
|
|
|
|
|
FILE_SUFFIX:'.html',
|
|
|
|
|
HAS_SOURCE: true
|
|
|
|
|
};
|
|
|
|
|
</script>
|
|
|
|
|
<script type="text/javascript" src="_static/jquery.js"></script>
|
|
|
|
|
<script type="text/javascript" src="_static/underscore.js"></script>
|
|
|
|
|
<script type="text/javascript" src="_static/doctools.js"></script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
|
|
|
|
|
<script type="text/javascript" src="_static/js/theme.js"></script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<link rel="top" title="nghttp2 0.3.0-DEV documentation" href="index.html"/>
|
|
|
|
|
<link rel="next" title="API Reference" href="apiref.html"/>
|
|
|
|
|
<link rel="prev" title="nghttp2 - HTTP/2.0 C Library" href="package_README.html"/>
|
|
|
|
|
|
|
|
|
|
<script src="//cdnjs.cloudflare.com/ajax/libs/modernizr/2.6.2/modernizr.min.js"></script>
|
|
|
|
|
|
|
|
|
|
</head>
|
|
|
|
|
|
|
|
|
|
<body class="wy-body-for-nav">
|
|
|
|
|
|
|
|
|
|
<div class="wy-grid-for-nav">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
|
|
|
|
|
<div class="wy-side-nav-search">
|
|
|
|
|
<a href="index.html" class="icon icon-home"> nghttp2</a>
|
|
|
|
|
<form id ="rtd-search-form" class="wy-form" action="search.html" method="get">
|
|
|
|
|
<input type="text" name="q" placeholder="Search docs" />
|
|
|
|
|
<input type="hidden" name="check_keywords" value="yes" />
|
|
|
|
|
<input type="hidden" name="area" value="default" />
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="wy-menu wy-menu-vertical" data-spy="affix">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<ul class="current">
|
|
|
|
|
<li class="toctree-l1"><a class="reference internal" href="package_README.html">nghttp2 - HTTP/2.0 C Library</a><ul>
|
|
|
|
|
<li class="toctree-l2"><a class="reference internal" href="package_README.html#development-status">Development Status</a></li>
|
|
|
|
|
<li class="toctree-l2"><a class="reference internal" href="package_README.html#public-test-server">Public Test Server</a></li>
|
|
|
|
|
<li class="toctree-l2"><a class="reference internal" href="package_README.html#requirements">Requirements</a></li>
|
|
|
|
|
<li class="toctree-l2"><a class="reference internal" href="package_README.html#build-from-git">Build from git</a></li>
|
|
|
|
|
<li class="toctree-l2"><a class="reference internal" href="package_README.html#building-documentation">Building documentation</a></li>
|
|
|
|
|
<li class="toctree-l2"><a class="reference internal" href="package_README.html#client-server-and-proxy-programs">Client, Server and Proxy programs</a></li>
|
|
|
|
|
<li class="toctree-l2"><a class="reference internal" href="package_README.html#header-compression-test-tools">Header compression test tools</a></li>
|
|
|
|
|
</ul>
|
|
|
|
|
</li>
|
|
|
|
|
<li class="toctree-l1 current"><a class="current reference internal" href="">Tutorial: HTTP/2.0 client</a></li>
|
|
|
|
|
<li class="toctree-l1"><a class="reference internal" href="apiref.html">API Reference</a><ul>
|
|
|
|
|
<li class="toctree-l2"><a class="reference internal" href="apiref.html#includes">Includes</a></li>
|
|
|
|
|
<li class="toctree-l2"><a class="reference internal" href="apiref.html#remarks">Remarks</a></li>
|
|
|
|
|
<li class="toctree-l2"><a class="reference internal" href="apiref.html#macros">Macros</a></li>
|
|
|
|
|
<li class="toctree-l2"><a class="reference internal" href="apiref.html#enums">Enums</a></li>
|
|
|
|
|
<li class="toctree-l2"><a class="reference internal" href="apiref.html#types-structs-unions-and-typedefs">Types (structs, unions and typedefs)</a></li>
|
|
|
|
|
<li class="toctree-l2"><a class="reference internal" href="apiref.html#functions">Functions</a></li>
|
|
|
|
|
</ul>
|
|
|
|
|
</li>
|
|
|
|
|
<li class="toctree-l1"><a class="reference internal" href="nghttp2.h.html">nghttp2.h</a></li>
|
|
|
|
|
<li class="toctree-l1"><a class="reference internal" href="nghttp2ver.h.html">nghttp2ver.h</a></li>
|
|
|
|
|
<li class="toctree-l1"><a class="reference external" href="https://github.com/tatsuhiro-t/nghttp2">Source</a></li>
|
|
|
|
|
<li class="toctree-l1"><a class="reference external" href="https://github.com/tatsuhiro-t/nghttp2/issues">Issues</a></li>
|
|
|
|
|
</ul>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
</nav>
|
|
|
|
|
|
|
|
|
|
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<nav class="wy-nav-top">
|
|
|
|
|
<i data-toggle="wy-nav-top" class="icon icon-reorder"></i>
|
|
|
|
|
<a href="index.html">nghttp2</a>
|
|
|
|
|
</nav>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="wy-nav-content">
|
|
|
|
|
<div class="rst-content">
|
|
|
|
|
<ul class="wy-breadcrumbs">
|
|
|
|
|
<li><a href="index.html">Docs</a> »</li>
|
|
|
|
|
<li><a href="">Tutorial: HTTP/2.0 client</a></li>
|
|
|
|
|
<li class="wy-breadcrumbs-aside">
|
|
|
|
|
|
|
|
|
|
<a href="_sources/tutorial-client.txt" rel="nofollow"> View page source</a>
|
|
|
|
|
|
|
|
|
|
</li>
|
|
|
|
|
</ul>
|
|
|
|
|
<hr/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="section" id="tutorial-http-2-0-client">
|
|
|
|
|
<h1>Tutorial: HTTP/2.0 client<a class="headerlink" href="#tutorial-http-2-0-client" title="Permalink to this headline">¶</a></h1>
|
|
|
|
|
<p>In this tutorial, we are going to write very primitive HTTP/2.0
|
|
|
|
|
client. The complete source code, libevent-client.c, is attached at
|
|
|
|
|
the end of this page. It also resides in examples directory in the
|
|
|
|
|
archive or repository.</p>
|
|
|
|
|
<p>This simple client takes 1 argument, HTTPS URI, and retrieves the
|
|
|
|
|
resource denoted by the URI. Its synopsis is like this:</p>
|
|
|
|
|
<div class="highlight-c"><pre>$ libevent-client HTTPS_URI</pre>
|
|
|
|
|
</div>
|
|
|
|
|
<p>We use libevent in this tutorial to handle networking I/O. Please
|
|
|
|
|
note that nghttp2 iteself does not depends on libevent.</p>
|
|
|
|
|
<p>First we do some setup routine for libevent and OpenSSL library in
|
|
|
|
|
function <tt class="docutils literal"><span class="pre">main()</span></tt> and <tt class="docutils literal"><span class="pre">run()</span></tt>, which is not so relevant to nghttp2
|
|
|
|
|
library use. The one thing you should look at is setup NPN callback.
|
|
|
|
|
The NPN callback is used for the client to select the next application
|
|
|
|
|
protocol over the SSL/TLS transport. In this tutorial, we use
|
|
|
|
|
<a class="reference internal" href="apiref.html#nghttp2_select_next_protocol" title="nghttp2_select_next_protocol"><tt class="xref c c-func docutils literal"><span class="pre">nghttp2_select_next_protocol()</span></tt></a> function to select the HTTP/2.0
|
|
|
|
|
protocol the library supports:</p>
|
|
|
|
|
<div class="highlight-c"><div class="highlight"><pre><span class="k">static</span> <span class="kt">int</span> <span class="nf">select_next_proto_cb</span><span class="p">(</span><span class="n">SSL</span><span class="o">*</span> <span class="n">ssl</span><span class="p">,</span>
|
|
|
|
|
<span class="kt">unsigned</span> <span class="kt">char</span> <span class="o">**</span><span class="n">out</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">char</span> <span class="o">*</span><span class="n">outlen</span><span class="p">,</span>
|
|
|
|
|
<span class="k">const</span> <span class="kt">unsigned</span> <span class="kt">char</span> <span class="o">*</span><span class="n">in</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">inlen</span><span class="p">,</span>
|
|
|
|
|
<span class="kt">void</span> <span class="o">*</span><span class="n">arg</span><span class="p">)</span>
|
|
|
|
|
<span class="p">{</span>
|
|
|
|
|
<span class="k">if</span><span class="p">(</span><span class="n">nghttp2_select_next_protocol</span><span class="p">(</span><span class="n">out</span><span class="p">,</span> <span class="n">outlen</span><span class="p">,</span> <span class="n">in</span><span class="p">,</span> <span class="n">inlen</span><span class="p">)</span> <span class="o"><=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
|
|
|
|
|
<span class="n">errx</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s">"Server did not advertise "</span> <span class="n">NGHTTP2_PROTO_VERSION_ID</span><span class="p">);</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
<span class="k">return</span> <span class="n">SSL_TLSEXT_ERR_OK</span><span class="p">;</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
</pre></div>
|
|
|
|
|
</div>
|
|
|
|
|
<p>The callback is set to the SSL_CTX object using
|
|
|
|
|
<tt class="docutils literal"><span class="pre">SSL_CTX_set_next_proto_select_cb()</span></tt> function:</p>
|
|
|
|
|
<div class="highlight-c"><div class="highlight"><pre><span class="k">static</span> <span class="n">SSL_CTX</span><span class="o">*</span> <span class="nf">create_ssl_ctx</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
|
|
|
|
|
<span class="p">{</span>
|
|
|
|
|
<span class="n">SSL_CTX</span> <span class="o">*</span><span class="n">ssl_ctx</span><span class="p">;</span>
|
|
|
|
|
<span class="n">ssl_ctx</span> <span class="o">=</span> <span class="n">SSL_CTX_new</span><span class="p">(</span><span class="n">SSLv23_client_method</span><span class="p">());</span>
|
|
|
|
|
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">ssl_ctx</span><span class="p">)</span> <span class="p">{</span>
|
|
|
|
|
<span class="n">errx</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s">"Could not create SSL/TLS context: %s"</span><span class="p">,</span>
|
|
|
|
|
<span class="n">ERR_error_string</span><span class="p">(</span><span class="n">ERR_get_error</span><span class="p">(),</span> <span class="nb">NULL</span><span class="p">));</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
<span class="n">SSL_CTX_set_options</span><span class="p">(</span><span class="n">ssl_ctx</span><span class="p">,</span>
|
|
|
|
|
<span class="n">SSL_OP_ALL</span> <span class="o">|</span> <span class="n">SSL_OP_NO_SSLv2</span> <span class="o">|</span> <span class="n">SSL_OP_NO_COMPRESSION</span> <span class="o">|</span>
|
|
|
|
|
<span class="n">SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION</span><span class="p">);</span>
|
|
|
|
|
<span class="n">SSL_CTX_set_next_proto_select_cb</span><span class="p">(</span><span class="n">ssl_ctx</span><span class="p">,</span> <span class="n">select_next_proto_cb</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
|
|
|
|
|
<span class="k">return</span> <span class="n">ssl_ctx</span><span class="p">;</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
</pre></div>
|
|
|
|
|
</div>
|
|
|
|
|
<p>We use <tt class="docutils literal"><span class="pre">http2_session_data</span></tt> structure to store the data related to
|
|
|
|
|
the HTTP/2.0 session:</p>
|
|
|
|
|
<div class="highlight-c"><div class="highlight"><pre><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
|
|
|
|
|
<span class="n">nghttp2_session</span> <span class="o">*</span><span class="n">session</span><span class="p">;</span>
|
|
|
|
|
<span class="k">struct</span> <span class="n">evdns_base</span> <span class="o">*</span><span class="n">dnsbase</span><span class="p">;</span>
|
|
|
|
|
<span class="k">struct</span> <span class="n">bufferevent</span> <span class="o">*</span><span class="n">bev</span><span class="p">;</span>
|
|
|
|
|
<span class="n">http2_stream_data</span> <span class="o">*</span><span class="n">stream_data</span><span class="p">;</span>
|
|
|
|
|
<span class="p">}</span> <span class="n">http2_session_data</span><span class="p">;</span>
|
|
|
|
|
</pre></div>
|
|
|
|
|
</div>
|
|
|
|
|
<p>Since this program only handles 1 URI, it uses only 1 stream. We store
|
|
|
|
|
its stream specific data in <tt class="docutils literal"><span class="pre">http2_stream_data</span></tt> structure and the
|
|
|
|
|
<tt class="docutils literal"><span class="pre">stream_data</span></tt> points to it. The <tt class="docutils literal"><span class="pre">struct</span> <span class="pre">http2_stream_data</span></tt> is
|
|
|
|
|
defined as follows:</p>
|
|
|
|
|
<div class="highlight-c"><div class="highlight"><pre><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
|
|
|
|
|
<span class="cm">/* The NULL-terminated URI string to retreive. */</span>
|
|
|
|
|
<span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">uri</span><span class="p">;</span>
|
|
|
|
|
<span class="cm">/* Parsed result of the |uri| */</span>
|
|
|
|
|
<span class="k">struct</span> <span class="n">http_parser_url</span> <span class="o">*</span><span class="n">u</span><span class="p">;</span>
|
|
|
|
|
<span class="cm">/* The authroity portion of the |uri|, not NULL-terminated */</span>
|
|
|
|
|
<span class="kt">char</span> <span class="o">*</span><span class="n">authority</span><span class="p">;</span>
|
|
|
|
|
<span class="cm">/* The path portion of the |uri|, including query, not</span>
|
|
|
|
|
<span class="cm"> NULL-terminated */</span>
|
|
|
|
|
<span class="kt">char</span> <span class="o">*</span><span class="n">path</span><span class="p">;</span>
|
|
|
|
|
<span class="cm">/* The length of the |authority| */</span>
|
|
|
|
|
<span class="kt">size_t</span> <span class="n">authoritylen</span><span class="p">;</span>
|
|
|
|
|
<span class="cm">/* The length of the |path| */</span>
|
|
|
|
|
<span class="kt">size_t</span> <span class="n">pathlen</span><span class="p">;</span>
|
|
|
|
|
<span class="cm">/* The stream ID of this stream */</span>
|
|
|
|
|
<span class="kt">int32_t</span> <span class="n">stream_id</span><span class="p">;</span>
|
|
|
|
|
<span class="p">}</span> <span class="n">http2_stream_data</span><span class="p">;</span>
|
|
|
|
|
</pre></div>
|
|
|
|
|
</div>
|
|
|
|
|
<p>We creates and initializes these structures in
|
|
|
|
|
<tt class="docutils literal"><span class="pre">create_http2_session_data()</span></tt> and <tt class="docutils literal"><span class="pre">create_http2_stream_data()</span></tt>
|
|
|
|
|
respectively.</p>
|
|
|
|
|
<p>Then we call function <tt class="docutils literal"><span class="pre">initiate_connection()</span></tt> to start connecting to
|
|
|
|
|
the remote server:</p>
|
|
|
|
|
<div class="highlight-c"><div class="highlight"><pre><span class="k">static</span> <span class="kt">void</span> <span class="nf">initiate_connection</span><span class="p">(</span><span class="k">struct</span> <span class="n">event_base</span> <span class="o">*</span><span class="n">evbase</span><span class="p">,</span>
|
|
|
|
|
<span class="n">SSL_CTX</span> <span class="o">*</span><span class="n">ssl_ctx</span><span class="p">,</span>
|
|
|
|
|
<span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">host</span><span class="p">,</span> <span class="kt">uint16_t</span> <span class="n">port</span><span class="p">,</span>
|
|
|
|
|
<span class="n">http2_session_data</span> <span class="o">*</span><span class="n">session_data</span><span class="p">)</span>
|
|
|
|
|
<span class="p">{</span>
|
|
|
|
|
<span class="kt">int</span> <span class="n">rv</span><span class="p">;</span>
|
|
|
|
|
<span class="k">struct</span> <span class="n">bufferevent</span> <span class="o">*</span><span class="n">bev</span><span class="p">;</span>
|
|
|
|
|
<span class="n">SSL</span> <span class="o">*</span><span class="n">ssl</span><span class="p">;</span>
|
|
|
|
|
|
|
|
|
|
<span class="n">ssl</span> <span class="o">=</span> <span class="n">create_ssl</span><span class="p">(</span><span class="n">ssl_ctx</span><span class="p">);</span>
|
|
|
|
|
<span class="n">bev</span> <span class="o">=</span> <span class="n">bufferevent_openssl_socket_new</span><span class="p">(</span><span class="n">evbase</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">ssl</span><span class="p">,</span>
|
|
|
|
|
<span class="n">BUFFEREVENT_SSL_CONNECTING</span><span class="p">,</span>
|
|
|
|
|
<span class="n">BEV_OPT_DEFER_CALLBACKS</span> <span class="o">|</span>
|
|
|
|
|
<span class="n">BEV_OPT_CLOSE_ON_FREE</span><span class="p">);</span>
|
|
|
|
|
<span class="n">bufferevent_setcb</span><span class="p">(</span><span class="n">bev</span><span class="p">,</span> <span class="n">readcb</span><span class="p">,</span> <span class="n">writecb</span><span class="p">,</span> <span class="n">eventcb</span><span class="p">,</span> <span class="n">session_data</span><span class="p">);</span>
|
|
|
|
|
<span class="n">rv</span> <span class="o">=</span> <span class="n">bufferevent_socket_connect_hostname</span><span class="p">(</span><span class="n">bev</span><span class="p">,</span> <span class="n">session_data</span><span class="o">-></span><span class="n">dnsbase</span><span class="p">,</span>
|
|
|
|
|
<span class="n">AF_UNSPEC</span><span class="p">,</span> <span class="n">host</span><span class="p">,</span> <span class="n">port</span><span class="p">);</span>
|
|
|
|
|
|
|
|
|
|
<span class="k">if</span><span class="p">(</span><span class="n">rv</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
|
|
|
|
|
<span class="n">errx</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s">"Could not connect to the remote host %s"</span><span class="p">,</span> <span class="n">host</span><span class="p">);</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
<span class="n">session_data</span><span class="o">-></span><span class="n">bev</span> <span class="o">=</span> <span class="n">bev</span><span class="p">;</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
</pre></div>
|
|
|
|
|
</div>
|
|
|
|
|
<p>We set 3 callbacks for the bufferevent: <tt class="docutils literal"><span class="pre">reacb</span></tt>, <tt class="docutils literal"><span class="pre">writecb</span></tt> and
|
|
|
|
|
<tt class="docutils literal"><span class="pre">eventcb</span></tt>.</p>
|
|
|
|
|
<p>The <tt class="docutils literal"><span class="pre">eventcb()</span></tt> is invoked by libevent event loop when an event
|
|
|
|
|
(e.g., connection established, timeout, etc) happens on the underlying
|
|
|
|
|
network socket:</p>
|
|
|
|
|
<div class="highlight-c"><div class="highlight"><pre><span class="k">static</span> <span class="kt">void</span> <span class="nf">eventcb</span><span class="p">(</span><span class="k">struct</span> <span class="n">bufferevent</span> <span class="o">*</span><span class="n">bev</span><span class="p">,</span> <span class="kt">short</span> <span class="n">events</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">ptr</span><span class="p">)</span>
|
|
|
|
|
<span class="p">{</span>
|
|
|
|
|
<span class="n">http2_session_data</span> <span class="o">*</span><span class="n">session_data</span> <span class="o">=</span> <span class="p">(</span><span class="n">http2_session_data</span><span class="o">*</span><span class="p">)</span><span class="n">ptr</span><span class="p">;</span>
|
|
|
|
|
<span class="k">if</span><span class="p">(</span><span class="n">events</span> <span class="o">&</span> <span class="n">BEV_EVENT_CONNECTED</span><span class="p">)</span> <span class="p">{</span>
|
|
|
|
|
<span class="kt">int</span> <span class="n">fd</span> <span class="o">=</span> <span class="n">bufferevent_getfd</span><span class="p">(</span><span class="n">bev</span><span class="p">);</span>
|
|
|
|
|
<span class="kt">int</span> <span class="n">val</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
|
|
|
|
|
<span class="n">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">"Connected</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
|
|
|
|
|
<span class="n">setsockopt</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">IPPROTO_TCP</span><span class="p">,</span> <span class="n">TCP_NODELAY</span><span class="p">,</span> <span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">val</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">val</span><span class="p">));</span>
|
|
|
|
|
<span class="n">initialize_nghttp2_session</span><span class="p">(</span><span class="n">session_data</span><span class="p">);</span>
|
|
|
|
|
<span class="n">send_client_connection_header</span><span class="p">(</span><span class="n">session_data</span><span class="p">);</span>
|
|
|
|
|
<span class="n">submit_request</span><span class="p">(</span><span class="n">session_data</span><span class="p">);</span>
|
|
|
|
|
<span class="k">if</span><span class="p">(</span><span class="n">session_send</span><span class="p">(</span><span class="n">session_data</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
|
|
|
|
|
<span class="n">delete_http2_session_data</span><span class="p">(</span><span class="n">session_data</span><span class="p">);</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
<span class="k">return</span><span class="p">;</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
<span class="k">if</span><span class="p">(</span><span class="n">events</span> <span class="o">&</span> <span class="n">BEV_EVENT_EOF</span><span class="p">)</span> <span class="p">{</span>
|
|
|
|
|
<span class="n">warnx</span><span class="p">(</span><span class="s">"Disconnected from the remote host"</span><span class="p">);</span>
|
|
|
|
|
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span><span class="p">(</span><span class="n">events</span> <span class="o">&</span> <span class="n">BEV_EVENT_ERROR</span><span class="p">)</span> <span class="p">{</span>
|
|
|
|
|
<span class="n">warnx</span><span class="p">(</span><span class="s">"Network error"</span><span class="p">);</span>
|
|
|
|
|
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span><span class="p">(</span><span class="n">events</span> <span class="o">&</span> <span class="n">BEV_EVENT_TIMEOUT</span><span class="p">)</span> <span class="p">{</span>
|
|
|
|
|
<span class="n">warnx</span><span class="p">(</span><span class="s">"Timeout"</span><span class="p">);</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
<span class="n">delete_http2_session_data</span><span class="p">(</span><span class="n">session_data</span><span class="p">);</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
</pre></div>
|
|
|
|
|
</div>
|
|
|
|
|
<p>For <tt class="docutils literal"><span class="pre">BEV_EVENT_EOF</span></tt>, <tt class="docutils literal"><span class="pre">BEV_EVENT_ERROR</span></tt> and <tt class="docutils literal"><span class="pre">BEV_EVENT_TIMEOUT</span></tt>
|
|
|
|
|
event, we just simply tear down the connection. The
|
|
|
|
|
<tt class="docutils literal"><span class="pre">BEV_EVENT_CONNECTED</span></tt> event is invoked when SSL/TLS handshake is
|
|
|
|
|
finished successfully. We first initialize nghttp2 session object in
|
|
|
|
|
<tt class="docutils literal"><span class="pre">initialize_nghttp2_session()</span></tt> function:</p>
|
|
|
|
|
<div class="highlight-c"><div class="highlight"><pre><span class="k">static</span> <span class="kt">void</span> <span class="nf">initialize_nghttp2_session</span><span class="p">(</span><span class="n">http2_session_data</span> <span class="o">*</span><span class="n">session_data</span><span class="p">)</span>
|
|
|
|
|
<span class="p">{</span>
|
|
|
|
|
<span class="n">nghttp2_session_callbacks</span> <span class="n">callbacks</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">};</span>
|
|
|
|
|
|
|
|
|
|
<span class="n">callbacks</span><span class="p">.</span><span class="n">send_callback</span> <span class="o">=</span> <span class="n">send_callback</span><span class="p">;</span>
|
|
|
|
|
<span class="n">callbacks</span><span class="p">.</span><span class="n">before_frame_send_callback</span> <span class="o">=</span> <span class="n">before_frame_send_callback</span><span class="p">;</span>
|
|
|
|
|
<span class="n">callbacks</span><span class="p">.</span><span class="n">on_frame_recv_callback</span> <span class="o">=</span> <span class="n">on_frame_recv_callback</span><span class="p">;</span>
|
|
|
|
|
<span class="n">callbacks</span><span class="p">.</span><span class="n">on_data_chunk_recv_callback</span> <span class="o">=</span> <span class="n">on_data_chunk_recv_callback</span><span class="p">;</span>
|
|
|
|
|
<span class="n">callbacks</span><span class="p">.</span><span class="n">on_stream_close_callback</span> <span class="o">=</span> <span class="n">on_stream_close_callback</span><span class="p">;</span>
|
|
|
|
|
<span class="n">nghttp2_session_client_new</span><span class="p">(</span><span class="o">&</span><span class="n">session_data</span><span class="o">-></span><span class="n">session</span><span class="p">,</span> <span class="o">&</span><span class="n">callbacks</span><span class="p">,</span> <span class="n">session_data</span><span class="p">);</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
</pre></div>
|
|
|
|
|
</div>
|
|
|
|
|
<p>Since we are creating client, we use <a class="reference internal" href="apiref.html#nghttp2_session_client_new" title="nghttp2_session_client_new"><tt class="xref c c-func docutils literal"><span class="pre">nghttp2_session_client_new()</span></tt></a> to
|
|
|
|
|
initialize nghttp2 session object. We setup 5 callbacks for the
|
|
|
|
|
nghttp2 session. We’ll explain these callbacks later.</p>
|
|
|
|
|
<p>The <tt class="xref c c-func docutils literal"><span class="pre">delete_http2_session_data()</span></tt> destroys <tt class="docutils literal"><span class="pre">session_data</span></tt> and frees
|
|
|
|
|
its bufferevent, so it closes underlying connection as well. It also
|
|
|
|
|
calls <a class="reference internal" href="apiref.html#nghttp2_session_del" title="nghttp2_session_del"><tt class="xref c c-func docutils literal"><span class="pre">nghttp2_session_del()</span></tt></a> to delete nghttp2 session object.</p>
|
|
|
|
|
<p>We begin HTTP/2.0 communication by sending client connection header,
|
|
|
|
|
which is 24 bytes magic byte sequence
|
|
|
|
|
(<a class="reference internal" href="apiref.html#NGHTTP2_CLIENT_CONNECTION_HEADER" title="NGHTTP2_CLIENT_CONNECTION_HEADER"><tt class="xref c c-macro docutils literal"><span class="pre">NGHTTP2_CLIENT_CONNECTION_HEADER</span></tt></a>) followed by SETTINGS
|
|
|
|
|
frame. The transmission of client connection header is done in
|
|
|
|
|
<tt class="docutils literal"><span class="pre">send_client_connection_header()</span></tt>:</p>
|
|
|
|
|
<div class="highlight-c"><div class="highlight"><pre><span class="k">static</span> <span class="kt">void</span> <span class="nf">send_client_connection_header</span><span class="p">(</span><span class="n">http2_session_data</span> <span class="o">*</span><span class="n">session_data</span><span class="p">)</span>
|
|
|
|
|
<span class="p">{</span>
|
|
|
|
|
<span class="n">nghttp2_settings_entry</span> <span class="n">iv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
|
|
|
|
|
<span class="p">{</span> <span class="n">NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS</span><span class="p">,</span> <span class="mi">100</span> <span class="p">}</span>
|
|
|
|
|
<span class="p">};</span>
|
|
|
|
|
<span class="kt">int</span> <span class="n">rv</span><span class="p">;</span>
|
|
|
|
|
|
|
|
|
|
<span class="n">bufferevent_write</span><span class="p">(</span><span class="n">session_data</span><span class="o">-></span><span class="n">bev</span><span class="p">,</span>
|
|
|
|
|
<span class="n">NGHTTP2_CLIENT_CONNECTION_HEADER</span><span class="p">,</span>
|
|
|
|
|
<span class="n">NGHTTP2_CLIENT_CONNECTION_HEADER_LEN</span><span class="p">);</span>
|
|
|
|
|
<span class="n">rv</span> <span class="o">=</span> <span class="n">nghttp2_submit_settings</span><span class="p">(</span><span class="n">session_data</span><span class="o">-></span><span class="n">session</span><span class="p">,</span> <span class="n">NGHTTP2_FLAG_NONE</span><span class="p">,</span>
|
|
|
|
|
<span class="n">iv</span><span class="p">,</span> <span class="n">ARRLEN</span><span class="p">(</span><span class="n">iv</span><span class="p">));</span>
|
|
|
|
|
<span class="k">if</span><span class="p">(</span><span class="n">rv</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
|
|
|
|
|
<span class="n">errx</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s">"Could not submit SETTINGS: %s"</span><span class="p">,</span> <span class="n">nghttp2_strerror</span><span class="p">(</span><span class="n">rv</span><span class="p">));</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
</pre></div>
|
|
|
|
|
</div>
|
|
|
|
|
<p>Here we specify max MAX_CONCURRENT_STREAMS to 100, which is really not
|
|
|
|
|
needed for this tiny example progoram, but we are demonstrating the
|
|
|
|
|
use of SETTINGS frame. To queue the SETTINGS frame for the
|
|
|
|
|
transmission, we use <a class="reference internal" href="apiref.html#nghttp2_submit_settings" title="nghttp2_submit_settings"><tt class="xref c c-func docutils literal"><span class="pre">nghttp2_submit_settings()</span></tt></a>. Note that
|
|
|
|
|
<a class="reference internal" href="apiref.html#nghttp2_submit_settings" title="nghttp2_submit_settings"><tt class="xref c c-func docutils literal"><span class="pre">nghttp2_submit_settings()</span></tt></a> function only queues the frame and not
|
|
|
|
|
actually send it. All <tt class="docutils literal"><span class="pre">nghttp2_submit_*()</span></tt> family functions have
|
|
|
|
|
this property. To actually send the frame, <a class="reference internal" href="apiref.html#nghttp2_session_send" title="nghttp2_session_send"><tt class="xref c c-func docutils literal"><span class="pre">nghttp2_session_send()</span></tt></a> is
|
|
|
|
|
used, which is described about later.</p>
|
|
|
|
|
<p>After the transmission of client connection header, we enqueue HTTP
|
|
|
|
|
request in <tt class="docutils literal"><span class="pre">submit_request()</span></tt> function:</p>
|
|
|
|
|
<div class="highlight-c"><div class="highlight"><pre><span class="k">static</span> <span class="kt">void</span> <span class="nf">submit_request</span><span class="p">(</span><span class="n">http2_session_data</span> <span class="o">*</span><span class="n">session_data</span><span class="p">)</span>
|
|
|
|
|
<span class="p">{</span>
|
|
|
|
|
<span class="kt">int</span> <span class="n">rv</span><span class="p">;</span>
|
|
|
|
|
<span class="n">http2_stream_data</span> <span class="o">*</span><span class="n">stream_data</span> <span class="o">=</span> <span class="n">session_data</span><span class="o">-></span><span class="n">stream_data</span><span class="p">;</span>
|
|
|
|
|
<span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">uri</span> <span class="o">=</span> <span class="n">stream_data</span><span class="o">-></span><span class="n">uri</span><span class="p">;</span>
|
|
|
|
|
<span class="k">const</span> <span class="k">struct</span> <span class="n">http_parser_url</span> <span class="o">*</span><span class="n">u</span> <span class="o">=</span> <span class="n">stream_data</span><span class="o">-></span><span class="n">u</span><span class="p">;</span>
|
|
|
|
|
<span class="n">nghttp2_nv</span> <span class="n">hdrs</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span>
|
|
|
|
|
<span class="n">MAKE_NV2</span><span class="p">(</span><span class="s">":method"</span><span class="p">,</span> <span class="s">"GET"</span><span class="p">),</span>
|
|
|
|
|
<span class="n">MAKE_NV</span><span class="p">(</span><span class="s">":scheme"</span><span class="p">,</span>
|
|
|
|
|
<span class="o">&</span><span class="n">uri</span><span class="p">[</span><span class="n">u</span><span class="o">-></span><span class="n">field_data</span><span class="p">[</span><span class="n">UF_SCHEMA</span><span class="p">].</span><span class="n">off</span><span class="p">],</span> <span class="n">u</span><span class="o">-></span><span class="n">field_data</span><span class="p">[</span><span class="n">UF_SCHEMA</span><span class="p">].</span><span class="n">len</span><span class="p">),</span>
|
|
|
|
|
<span class="n">MAKE_NV</span><span class="p">(</span><span class="s">":authority"</span><span class="p">,</span> <span class="n">stream_data</span><span class="o">-></span><span class="n">authority</span><span class="p">,</span> <span class="n">stream_data</span><span class="o">-></span><span class="n">authoritylen</span><span class="p">),</span>
|
|
|
|
|
<span class="n">MAKE_NV</span><span class="p">(</span><span class="s">":path"</span><span class="p">,</span> <span class="n">stream_data</span><span class="o">-></span><span class="n">path</span><span class="p">,</span> <span class="n">stream_data</span><span class="o">-></span><span class="n">pathlen</span><span class="p">)</span>
|
|
|
|
|
<span class="p">};</span>
|
|
|
|
|
<span class="n">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">"Request headers:</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
|
|
|
|
|
<span class="n">print_headers</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="n">hdrs</span><span class="p">,</span> <span class="n">ARRLEN</span><span class="p">(</span><span class="n">hdrs</span><span class="p">));</span>
|
|
|
|
|
<span class="n">rv</span> <span class="o">=</span> <span class="n">nghttp2_submit_request</span><span class="p">(</span><span class="n">session_data</span><span class="o">-></span><span class="n">session</span><span class="p">,</span> <span class="n">NGHTTP2_PRI_DEFAULT</span><span class="p">,</span>
|
|
|
|
|
<span class="n">hdrs</span><span class="p">,</span> <span class="n">ARRLEN</span><span class="p">(</span><span class="n">hdrs</span><span class="p">),</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">stream_data</span><span class="p">);</span>
|
|
|
|
|
<span class="k">if</span><span class="p">(</span><span class="n">rv</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
|
|
|
|
|
<span class="n">errx</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s">"Could not submit HTTP request: %s"</span><span class="p">,</span> <span class="n">nghttp2_strerror</span><span class="p">(</span><span class="n">rv</span><span class="p">));</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
</pre></div>
|
|
|
|
|
</div>
|
|
|
|
|
<p>We build HTTP request header fields in <tt class="docutils literal"><span class="pre">hdrs</span></tt> which is an array of
|
|
|
|
|
<a class="reference internal" href="apiref.html#nghttp2_nv" title="nghttp2_nv"><tt class="xref c c-type docutils literal"><span class="pre">nghttp2_nv</span></tt></a>. There are 4 header fields to be sent: <tt class="docutils literal"><span class="pre">:method</span></tt>,
|
|
|
|
|
<tt class="docutils literal"><span class="pre">:scheme</span></tt>, <tt class="docutils literal"><span class="pre">:authority</span></tt> and <tt class="docutils literal"><span class="pre">:path</span></tt>. To queue this HTTP request,
|
|
|
|
|
we use <a class="reference internal" href="apiref.html#nghttp2_submit_request" title="nghttp2_submit_request"><tt class="xref c c-func docutils literal"><span class="pre">nghttp2_submit_request()</span></tt></a> function. The <tt class="xref c c-func docutils literal"><span class="pre">stream_data()</span></tt> is
|
|
|
|
|
passed in <em>stream_user_data</em> parameter. It is used in nghttp2
|
|
|
|
|
callbacks which we’ll describe about later.</p>
|
|
|
|
|
<p>The next bufferevent callback is <tt class="docutils literal"><span class="pre">readcb()</span></tt>, which is invoked when
|
|
|
|
|
data is available to read in the bufferevent input buffer:</p>
|
|
|
|
|
<div class="highlight-c"><div class="highlight"><pre><span class="k">static</span> <span class="kt">void</span> <span class="nf">readcb</span><span class="p">(</span><span class="k">struct</span> <span class="n">bufferevent</span> <span class="o">*</span><span class="n">bev</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">ptr</span><span class="p">)</span>
|
|
|
|
|
<span class="p">{</span>
|
|
|
|
|
<span class="n">http2_session_data</span> <span class="o">*</span><span class="n">session_data</span> <span class="o">=</span> <span class="p">(</span><span class="n">http2_session_data</span><span class="o">*</span><span class="p">)</span><span class="n">ptr</span><span class="p">;</span>
|
|
|
|
|
<span class="kt">int</span> <span class="n">rv</span><span class="p">;</span>
|
|
|
|
|
<span class="k">struct</span> <span class="n">evbuffer</span> <span class="o">*</span><span class="n">input</span> <span class="o">=</span> <span class="n">bufferevent_get_input</span><span class="p">(</span><span class="n">bev</span><span class="p">);</span>
|
|
|
|
|
<span class="kt">size_t</span> <span class="n">datalen</span> <span class="o">=</span> <span class="n">evbuffer_get_length</span><span class="p">(</span><span class="n">input</span><span class="p">);</span>
|
|
|
|
|
<span class="kt">unsigned</span> <span class="kt">char</span> <span class="o">*</span><span class="n">data</span> <span class="o">=</span> <span class="n">evbuffer_pullup</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">);</span>
|
|
|
|
|
<span class="n">rv</span> <span class="o">=</span> <span class="n">nghttp2_session_mem_recv</span><span class="p">(</span><span class="n">session_data</span><span class="o">-></span><span class="n">session</span><span class="p">,</span> <span class="n">data</span><span class="p">,</span> <span class="n">datalen</span><span class="p">);</span>
|
|
|
|
|
<span class="k">if</span><span class="p">(</span><span class="n">rv</span> <span class="o"><</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
|
|
|
|
|
<span class="n">warnx</span><span class="p">(</span><span class="s">"Fatal error: %s"</span><span class="p">,</span> <span class="n">nghttp2_strerror</span><span class="p">(</span><span class="n">rv</span><span class="p">));</span>
|
|
|
|
|
<span class="n">delete_http2_session_data</span><span class="p">(</span><span class="n">session_data</span><span class="p">);</span>
|
|
|
|
|
<span class="k">return</span><span class="p">;</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
<span class="n">evbuffer_drain</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">rv</span><span class="p">);</span>
|
|
|
|
|
<span class="k">if</span><span class="p">(</span><span class="n">session_send</span><span class="p">(</span><span class="n">session_data</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
|
|
|
|
|
<span class="n">delete_http2_session_data</span><span class="p">(</span><span class="n">session_data</span><span class="p">);</span>
|
|
|
|
|
<span class="k">return</span><span class="p">;</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
</pre></div>
|
|
|
|
|
</div>
|
|
|
|
|
<p>In this function, we feed all unprocessed, received data to nghttp2
|
|
|
|
|
session object using <a class="reference internal" href="apiref.html#nghttp2_session_mem_recv" title="nghttp2_session_mem_recv"><tt class="xref c c-func docutils literal"><span class="pre">nghttp2_session_mem_recv()</span></tt></a> function. The
|
|
|
|
|
<a class="reference internal" href="apiref.html#nghttp2_session_mem_recv" title="nghttp2_session_mem_recv"><tt class="xref c c-func docutils literal"><span class="pre">nghttp2_session_mem_recv()</span></tt></a> processes the received data and may
|
|
|
|
|
invoke nghttp2 callbacks and also queue frames. Since there may be
|
|
|
|
|
pending frames, we call <tt class="docutils literal"><span class="pre">session_send()</span></tt> function to send those
|
|
|
|
|
frames. The <tt class="docutils literal"><span class="pre">session_send()</span></tt> function is defined as follows:</p>
|
|
|
|
|
<div class="highlight-c"><div class="highlight"><pre><span class="k">static</span> <span class="kt">int</span> <span class="nf">session_send</span><span class="p">(</span><span class="n">http2_session_data</span> <span class="o">*</span><span class="n">session_data</span><span class="p">)</span>
|
|
|
|
|
<span class="p">{</span>
|
|
|
|
|
<span class="kt">int</span> <span class="n">rv</span><span class="p">;</span>
|
|
|
|
|
|
|
|
|
|
<span class="n">rv</span> <span class="o">=</span> <span class="n">nghttp2_session_send</span><span class="p">(</span><span class="n">session_data</span><span class="o">-></span><span class="n">session</span><span class="p">);</span>
|
|
|
|
|
<span class="k">if</span><span class="p">(</span><span class="n">rv</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
|
|
|
|
|
<span class="n">warnx</span><span class="p">(</span><span class="s">"Fatal error: %s"</span><span class="p">,</span> <span class="n">nghttp2_strerror</span><span class="p">(</span><span class="n">rv</span><span class="p">));</span>
|
|
|
|
|
<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
</pre></div>
|
|
|
|
|
</div>
|
|
|
|
|
<p>The <a class="reference internal" href="apiref.html#nghttp2_session_send" title="nghttp2_session_send"><tt class="xref c c-func docutils literal"><span class="pre">nghttp2_session_send()</span></tt></a> function serializes the frame into wire
|
|
|
|
|
format and call <tt class="xref c c-member docutils literal"><span class="pre">nghttp2_callbacks.nghttp2_send_callback</span></tt> with
|
|
|
|
|
it. We set <tt class="docutils literal"><span class="pre">send_callback()</span></tt> function as
|
|
|
|
|
<a class="reference internal" href="apiref.html#nghttp2_session_callbacks.send_callback" title="nghttp2_session_callbacks.send_callback"><tt class="xref c c-member docutils literal"><span class="pre">nghttp2_session_callbacks.send_callback</span></tt></a> in
|
|
|
|
|
<tt class="docutils literal"><span class="pre">initialize_nghttp2_session()</span></tt> function described earlier. It is
|
|
|
|
|
defined as follows:</p>
|
|
|
|
|
<div class="highlight-c"><div class="highlight"><pre><span class="k">static</span> <span class="kt">ssize_t</span> <span class="nf">send_callback</span><span class="p">(</span><span class="n">nghttp2_session</span> <span class="o">*</span><span class="n">session</span><span class="p">,</span>
|
|
|
|
|
<span class="k">const</span> <span class="kt">uint8_t</span> <span class="o">*</span><span class="n">data</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">length</span><span class="p">,</span>
|
|
|
|
|
<span class="kt">int</span> <span class="n">flags</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">user_data</span><span class="p">)</span>
|
|
|
|
|
<span class="p">{</span>
|
|
|
|
|
<span class="n">http2_session_data</span> <span class="o">*</span><span class="n">session_data</span> <span class="o">=</span> <span class="p">(</span><span class="n">http2_session_data</span><span class="o">*</span><span class="p">)</span><span class="n">user_data</span><span class="p">;</span>
|
|
|
|
|
<span class="k">struct</span> <span class="n">bufferevent</span> <span class="o">*</span><span class="n">bev</span> <span class="o">=</span> <span class="n">session_data</span><span class="o">-></span><span class="n">bev</span><span class="p">;</span>
|
|
|
|
|
<span class="n">bufferevent_write</span><span class="p">(</span><span class="n">bev</span><span class="p">,</span> <span class="n">data</span><span class="p">,</span> <span class="n">length</span><span class="p">);</span>
|
|
|
|
|
<span class="k">return</span> <span class="n">length</span><span class="p">;</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
</pre></div>
|
|
|
|
|
</div>
|
|
|
|
|
<p>Since we use bufferevent to abstract network I/O, we just write the
|
|
|
|
|
data to the bufferevent object. Note that <a class="reference internal" href="apiref.html#nghttp2_session_send" title="nghttp2_session_send"><tt class="xref c c-func docutils literal"><span class="pre">nghttp2_session_send()</span></tt></a>
|
|
|
|
|
continues to write all frames queued so far. If we were writing the
|
|
|
|
|
data to the non-blocking socket directly using <tt class="docutils literal"><span class="pre">write()</span></tt> system call
|
|
|
|
|
in the <a class="reference internal" href="apiref.html#nghttp2_session_callbacks.send_callback" title="nghttp2_session_callbacks.send_callback"><tt class="xref c c-member docutils literal"><span class="pre">nghttp2_session_callbacks.send_callback</span></tt></a>, we will
|
|
|
|
|
surely get <tt class="docutils literal"><span class="pre">EAGAIN</span></tt> or <tt class="docutils literal"><span class="pre">EWOULDBLOCK</span></tt> since the socket has limited
|
|
|
|
|
send buffer. If that happens, we can return
|
|
|
|
|
<a class="reference internal" href="apiref.html#NGHTTP2_ERR_WOULDBLOCK" title="NGHTTP2_ERR_WOULDBLOCK"><tt class="xref c c-macro docutils literal"><span class="pre">NGHTTP2_ERR_WOULDBLOCK</span></tt></a> to signal the nghttp2 library to stop
|
|
|
|
|
sending further data. But writing to the bufferevent, we have to
|
|
|
|
|
regulate the amount data to be buffered by ourselves to avoid possible
|
|
|
|
|
huge memory consumption. In this example client, we do not limit
|
|
|
|
|
anything. To see how to regulate the amount of buffered data, see the
|
|
|
|
|
<tt class="docutils literal"><span class="pre">send_callback()</span></tt> in the server tutorial.</p>
|
|
|
|
|
<p>The third bufferevent callback is <tt class="docutils literal"><span class="pre">writecb()</span></tt>, which is invoked when
|
|
|
|
|
all data written in the bufferevent output buffer have been sent:</p>
|
|
|
|
|
<div class="highlight-c"><div class="highlight"><pre><span class="k">static</span> <span class="kt">void</span> <span class="nf">writecb</span><span class="p">(</span><span class="k">struct</span> <span class="n">bufferevent</span> <span class="o">*</span><span class="n">bev</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">ptr</span><span class="p">)</span>
|
|
|
|
|
<span class="p">{</span>
|
|
|
|
|
<span class="n">http2_session_data</span> <span class="o">*</span><span class="n">session_data</span> <span class="o">=</span> <span class="p">(</span><span class="n">http2_session_data</span><span class="o">*</span><span class="p">)</span><span class="n">ptr</span><span class="p">;</span>
|
|
|
|
|
<span class="k">if</span><span class="p">(</span><span class="n">nghttp2_session_want_read</span><span class="p">(</span><span class="n">session_data</span><span class="o">-></span><span class="n">session</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">&&</span>
|
|
|
|
|
<span class="n">nghttp2_session_want_write</span><span class="p">(</span><span class="n">session_data</span><span class="o">-></span><span class="n">session</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">&&</span>
|
|
|
|
|
<span class="n">evbuffer_get_length</span><span class="p">(</span><span class="n">bufferevent_get_output</span><span class="p">(</span><span class="n">session_data</span><span class="o">-></span><span class="n">bev</span><span class="p">))</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
|
|
|
|
|
<span class="n">delete_http2_session_data</span><span class="p">(</span><span class="n">session_data</span><span class="p">);</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
</pre></div>
|
|
|
|
|
</div>
|
|
|
|
|
<p>As described earlier, we just write off all data in <tt class="xref c c-func docutils literal"><span class="pre">send_callback()</span></tt>,
|
|
|
|
|
we have no data to write in this function. All we have to do is check
|
|
|
|
|
we have to drop connection or not. The nghttp2 session object keeps
|
|
|
|
|
track of reception and transmission of GOAWAY frame and other error
|
|
|
|
|
conditions as well. Using these information, nghttp2 session object
|
|
|
|
|
will tell whether the connection should be dropped or not. More
|
|
|
|
|
specifically, both <a class="reference internal" href="apiref.html#nghttp2_session_want_read" title="nghttp2_session_want_read"><tt class="xref c c-func docutils literal"><span class="pre">nghttp2_session_want_read()</span></tt></a> and
|
|
|
|
|
<a class="reference internal" href="apiref.html#nghttp2_session_want_write" title="nghttp2_session_want_write"><tt class="xref c c-func docutils literal"><span class="pre">nghttp2_session_want_write()</span></tt></a> return 0, we have no business in the
|
|
|
|
|
connection. But since we have using bufferevent and its deferred
|
|
|
|
|
callback option, the bufferevent output buffer may contain the pending
|
|
|
|
|
data when the <tt class="docutils literal"><span class="pre">writecb()</span></tt> is called. To handle this situation, we
|
|
|
|
|
also check whether the output buffer is empty or not. If these
|
|
|
|
|
conditions are met, we drop connection.</p>
|
|
|
|
|
<p>We have already described about nghttp2 callback <tt class="docutils literal"><span class="pre">send_callback()</span></tt>.
|
|
|
|
|
Let’s describe remaining nghttp2 callbacks we setup in
|
|
|
|
|
<tt class="docutils literal"><span class="pre">initialize_nghttp2_setup()</span></tt> function.</p>
|
|
|
|
|
<p>The <tt class="xref c c-func docutils literal"><span class="pre">before_frame_send_callback()</span></tt> function is invoked when a frame is
|
|
|
|
|
about to be sent:</p>
|
|
|
|
|
<div class="highlight-c"><div class="highlight"><pre><span class="k">static</span> <span class="kt">int</span> <span class="nf">before_frame_send_callback</span>
|
|
|
|
|
<span class="p">(</span><span class="n">nghttp2_session</span> <span class="o">*</span><span class="n">session</span><span class="p">,</span> <span class="k">const</span> <span class="n">nghttp2_frame</span> <span class="o">*</span><span class="n">frame</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">user_data</span><span class="p">)</span>
|
|
|
|
|
<span class="p">{</span>
|
|
|
|
|
<span class="n">http2_session_data</span> <span class="o">*</span><span class="n">session_data</span> <span class="o">=</span> <span class="p">(</span><span class="n">http2_session_data</span><span class="o">*</span><span class="p">)</span><span class="n">user_data</span><span class="p">;</span>
|
|
|
|
|
<span class="n">http2_stream_data</span> <span class="o">*</span><span class="n">stream_data</span><span class="p">;</span>
|
|
|
|
|
|
|
|
|
|
<span class="k">if</span><span class="p">(</span><span class="n">frame</span><span class="o">-></span><span class="n">hd</span><span class="p">.</span><span class="n">type</span> <span class="o">==</span> <span class="n">NGHTTP2_HEADERS</span> <span class="o">&&</span>
|
|
|
|
|
<span class="n">frame</span><span class="o">-></span><span class="n">headers</span><span class="p">.</span><span class="n">cat</span> <span class="o">==</span> <span class="n">NGHTTP2_HCAT_REQUEST</span><span class="p">)</span> <span class="p">{</span>
|
|
|
|
|
<span class="n">stream_data</span> <span class="o">=</span>
|
|
|
|
|
<span class="p">(</span><span class="n">http2_stream_data</span><span class="o">*</span><span class="p">)</span><span class="n">nghttp2_session_get_stream_user_data</span>
|
|
|
|
|
<span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="n">frame</span><span class="o">-></span><span class="n">hd</span><span class="p">.</span><span class="n">stream_id</span><span class="p">);</span>
|
|
|
|
|
<span class="k">if</span><span class="p">(</span><span class="n">stream_data</span> <span class="o">==</span> <span class="n">session_data</span><span class="o">-></span><span class="n">stream_data</span><span class="p">)</span> <span class="p">{</span>
|
|
|
|
|
<span class="n">stream_data</span><span class="o">-></span><span class="n">stream_id</span> <span class="o">=</span> <span class="n">frame</span><span class="o">-></span><span class="n">hd</span><span class="p">.</span><span class="n">stream_id</span><span class="p">;</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
</pre></div>
|
|
|
|
|
</div>
|
|
|
|
|
<p>Remember that we have not get stream ID when we submit HTTP request
|
|
|
|
|
using <a class="reference internal" href="apiref.html#nghttp2_submit_request" title="nghttp2_submit_request"><tt class="xref c c-func docutils literal"><span class="pre">nghttp2_submit_request()</span></tt></a>. Since nghttp2 library reorders the
|
|
|
|
|
request based on priority and stream ID must be monotonically
|
|
|
|
|
increased, the stream ID is not assigned just before transmission.
|
|
|
|
|
The one of the purpose of this callback is get the stream ID assigned
|
|
|
|
|
to the frame. First we check that the frame is HEADERS frame. Since
|
|
|
|
|
HEADERS has several meanings in HTTP/2.0, we check that it is request
|
|
|
|
|
HEADERS (which means that the first HEADERS frame to create a stream).
|
|
|
|
|
The assigned stream ID is <tt class="docutils literal"><span class="pre">frame->hd.stream_id</span></tt>. Recall that we
|
|
|
|
|
passed <tt class="docutils literal"><span class="pre">stream_data</span></tt> in the <em>stream_user_data</em> parameter of
|
|
|
|
|
<a class="reference internal" href="apiref.html#nghttp2_submit_request" title="nghttp2_submit_request"><tt class="xref c c-func docutils literal"><span class="pre">nghttp2_submit_request()</span></tt></a> function. We can get it using
|
|
|
|
|
<a class="reference internal" href="apiref.html#nghttp2_session_get_stream_user_data" title="nghttp2_session_get_stream_user_data"><tt class="xref c c-func docutils literal"><span class="pre">nghttp2_session_get_stream_user_data()</span></tt></a> function. To really sure that
|
|
|
|
|
this HEADERS frame is the request HEADERS we have queued, we check
|
|
|
|
|
that <tt class="docutils literal"><span class="pre">session_data->stream_data</span></tt> and <tt class="docutils literal"><span class="pre">stream_data</span></tt> returned from
|
|
|
|
|
<a class="reference internal" href="apiref.html#nghttp2_session_get_stream_user_data" title="nghttp2_session_get_stream_user_data"><tt class="xref c c-func docutils literal"><span class="pre">nghttp2_session_get_stream_user_data()</span></tt></a> are pointing the same
|
|
|
|
|
location. In this example program, we just only uses 1 stream, it is
|
|
|
|
|
unnecessary to compare them, but real applications surely deal with
|
|
|
|
|
multiple streams, and <em>stream_user_data</em> is very handy to identify
|
|
|
|
|
which HEADERS we are seeing in the callback. Therefore we just show
|
|
|
|
|
how to use it here.</p>
|
|
|
|
|
<p>The <tt class="docutils literal"><span class="pre">on_frame_recv_callback()</span></tt> function is invoked when a frame is
|
|
|
|
|
received from the remote peer:</p>
|
|
|
|
|
<div class="highlight-c"><div class="highlight"><pre><span class="k">static</span> <span class="kt">int</span> <span class="nf">on_frame_recv_callback</span><span class="p">(</span><span class="n">nghttp2_session</span> <span class="o">*</span><span class="n">session</span><span class="p">,</span>
|
|
|
|
|
<span class="k">const</span> <span class="n">nghttp2_frame</span> <span class="o">*</span><span class="n">frame</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">user_data</span><span class="p">)</span>
|
|
|
|
|
<span class="p">{</span>
|
|
|
|
|
<span class="n">http2_session_data</span> <span class="o">*</span><span class="n">session_data</span> <span class="o">=</span> <span class="p">(</span><span class="n">http2_session_data</span><span class="o">*</span><span class="p">)</span><span class="n">user_data</span><span class="p">;</span>
|
|
|
|
|
<span class="k">switch</span><span class="p">(</span><span class="n">frame</span><span class="o">-></span><span class="n">hd</span><span class="p">.</span><span class="n">type</span><span class="p">)</span> <span class="p">{</span>
|
|
|
|
|
<span class="k">case</span> <span class="n">NGHTTP2_HEADERS</span>:
|
|
|
|
|
<span class="k">if</span><span class="p">(</span><span class="n">frame</span><span class="o">-></span><span class="n">headers</span><span class="p">.</span><span class="n">cat</span> <span class="o">==</span> <span class="n">NGHTTP2_HCAT_RESPONSE</span> <span class="o">&&</span>
|
|
|
|
|
<span class="n">session_data</span><span class="o">-></span><span class="n">stream_data</span><span class="o">-></span><span class="n">stream_id</span> <span class="o">==</span> <span class="n">frame</span><span class="o">-></span><span class="n">hd</span><span class="p">.</span><span class="n">stream_id</span><span class="p">)</span> <span class="p">{</span>
|
|
|
|
|
<span class="cm">/* Print response headers for the initiated request. */</span>
|
|
|
|
|
<span class="n">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">"Response headers:</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
|
|
|
|
|
<span class="n">print_headers</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="n">frame</span><span class="o">-></span><span class="n">headers</span><span class="p">.</span><span class="n">nva</span><span class="p">,</span> <span class="n">frame</span><span class="o">-></span><span class="n">headers</span><span class="p">.</span><span class="n">nvlen</span><span class="p">);</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
<span class="k">break</span><span class="p">;</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
</pre></div>
|
|
|
|
|
</div>
|
|
|
|
|
<p>In this tutorial, we are just interested in the HTTP response
|
|
|
|
|
HEADERS. We check te frame type and its category (it should be
|
|
|
|
|
<a class="reference internal" href="apiref.html#NGHTTP2_HCAT_RESPONSE" title="NGHTTP2_HCAT_RESPONSE"><tt class="xref c c-macro docutils literal"><span class="pre">NGHTTP2_HCAT_RESPONSE</span></tt></a> for HTTP response HEADERS). Also check
|
|
|
|
|
its stream ID.</p>
|
|
|
|
|
<p>The <tt class="docutils literal"><span class="pre">on_data_chunk_recv_callback()</span></tt> function is invoked when a chunk
|
|
|
|
|
of data is received from the remote peer:</p>
|
|
|
|
|
<div class="highlight-c"><div class="highlight"><pre><span class="k">static</span> <span class="kt">int</span> <span class="nf">on_data_chunk_recv_callback</span><span class="p">(</span><span class="n">nghttp2_session</span> <span class="o">*</span><span class="n">session</span><span class="p">,</span> <span class="kt">uint8_t</span> <span class="n">flags</span><span class="p">,</span>
|
|
|
|
|
<span class="kt">int32_t</span> <span class="n">stream_id</span><span class="p">,</span>
|
|
|
|
|
<span class="k">const</span> <span class="kt">uint8_t</span> <span class="o">*</span><span class="n">data</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">len</span><span class="p">,</span>
|
|
|
|
|
<span class="kt">void</span> <span class="o">*</span><span class="n">user_data</span><span class="p">)</span>
|
|
|
|
|
<span class="p">{</span>
|
|
|
|
|
<span class="n">http2_session_data</span> <span class="o">*</span><span class="n">session_data</span> <span class="o">=</span> <span class="p">(</span><span class="n">http2_session_data</span><span class="o">*</span><span class="p">)</span><span class="n">user_data</span><span class="p">;</span>
|
|
|
|
|
<span class="k">if</span><span class="p">(</span><span class="n">session_data</span><span class="o">-></span><span class="n">stream_data</span><span class="o">-></span><span class="n">stream_id</span> <span class="o">==</span> <span class="n">stream_id</span><span class="p">)</span> <span class="p">{</span>
|
|
|
|
|
<span class="n">fwrite</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">len</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">stdout</span><span class="p">);</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
</pre></div>
|
|
|
|
|
</div>
|
|
|
|
|
<p>In our case, a chunk of data is response body. After checking stream
|
|
|
|
|
ID, we just write the recieved data to the stdout. Note that the
|
|
|
|
|
output in the terminal may be corrupted if the response body contains
|
|
|
|
|
some binary data.</p>
|
|
|
|
|
<p>The <tt class="docutils literal"><span class="pre">on_stream_close_callback()</span></tt> function is invoked when the stream
|
|
|
|
|
is about to close:</p>
|
|
|
|
|
<div class="highlight-c"><div class="highlight"><pre><span class="k">static</span> <span class="kt">int</span> <span class="nf">on_stream_close_callback</span><span class="p">(</span><span class="n">nghttp2_session</span> <span class="o">*</span><span class="n">session</span><span class="p">,</span>
|
|
|
|
|
<span class="kt">int32_t</span> <span class="n">stream_id</span><span class="p">,</span>
|
|
|
|
|
<span class="n">nghttp2_error_code</span> <span class="n">error_code</span><span class="p">,</span>
|
|
|
|
|
<span class="kt">void</span> <span class="o">*</span><span class="n">user_data</span><span class="p">)</span>
|
|
|
|
|
<span class="p">{</span>
|
|
|
|
|
<span class="n">http2_session_data</span> <span class="o">*</span><span class="n">session_data</span> <span class="o">=</span> <span class="p">(</span><span class="n">http2_session_data</span><span class="o">*</span><span class="p">)</span><span class="n">user_data</span><span class="p">;</span>
|
|
|
|
|
<span class="kt">int</span> <span class="n">rv</span><span class="p">;</span>
|
|
|
|
|
|
|
|
|
|
<span class="k">if</span><span class="p">(</span><span class="n">session_data</span><span class="o">-></span><span class="n">stream_data</span><span class="o">-></span><span class="n">stream_id</span> <span class="o">==</span> <span class="n">stream_id</span><span class="p">)</span> <span class="p">{</span>
|
|
|
|
|
<span class="n">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">"Stream %d closed with error_code=%d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span>
|
|
|
|
|
<span class="n">stream_id</span><span class="p">,</span> <span class="n">error_code</span><span class="p">);</span>
|
|
|
|
|
<span class="n">rv</span> <span class="o">=</span> <span class="n">nghttp2_session_fail_session</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="n">NGHTTP2_NO_ERROR</span><span class="p">);</span>
|
|
|
|
|
<span class="k">if</span><span class="p">(</span><span class="n">rv</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
|
|
|
|
|
<span class="k">return</span> <span class="n">NGHTTP2_ERR_CALLBACK_FAILURE</span><span class="p">;</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
|
|
|
|
|
<span class="p">}</span>
|
|
|
|
|
</pre></div>
|
|
|
|
|
</div>
|
|
|
|
|
<p>If the stream ID matches the one we initiated, it means that its
|
|
|
|
|
stream is going to be closed. Since we have finished to get the
|
|
|
|
|
resource we want (or the stream was reset by RST_STREAM from the
|
|
|
|
|
remote peer), we call <a class="reference internal" href="apiref.html#nghttp2_session_fail_session" title="nghttp2_session_fail_session"><tt class="xref c c-func docutils literal"><span class="pre">nghttp2_session_fail_session()</span></tt></a> to commencing
|
|
|
|
|
the closure of the HTTP/2.0 session gracefully. If you have some data
|
|
|
|
|
associated for the stream to be closed, you may delete it here.</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<footer>
|
|
|
|
|
|
|
|
|
|
<div class="rst-footer-buttons">
|
|
|
|
|
|
|
|
|
|
<a href="apiref.html" class="btn btn-neutral float-right" title="API Reference"/>Next <span class="icon icon-circle-arrow-right"></span></a>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<a href="package_README.html" class="btn btn-neutral" title="nghttp2 - HTTP/2.0 C Library"><span class="icon icon-circle-arrow-left"></span> Previous</a>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<hr/>
|
|
|
|
|
|
|
|
|
|
<p>
|
|
|
|
|
© Copyright 2012, 2013, Tatsuhiro Tsujikawa.
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<a href="https://www.github.com/snide/sphinx_rtd_theme">Sphinx theme</a> provided by <a href="http://readthedocs.org">Read the Docs</a>
|
|
|
|
|
</footer>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|