From 328ad0a41c6bdf596224ff2e9ab9c0fabde8634d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 09:32:08 +0200 Subject: Adding upstream version 0.8.0. Signed-off-by: Daniel Baumann --- doc/.gitignore | 1 + doc/Makefile.am | 51 ++++++ doc/make.bat | 35 ++++ doc/mkapiref.py | 358 +++++++++++++++++++++++++++++++++++++++ doc/source/.gitignore | 5 + doc/source/conf.py.in | 94 ++++++++++ doc/source/index.rst | 22 +++ doc/source/programmers-guide.rst | 207 ++++++++++++++++++++++ doc/source/qpack-howto.rst | 85 ++++++++++ 9 files changed, 858 insertions(+) create mode 100644 doc/.gitignore create mode 100644 doc/Makefile.am create mode 100644 doc/make.bat create mode 100755 doc/mkapiref.py create mode 100644 doc/source/.gitignore create mode 100644 doc/source/conf.py.in create mode 100644 doc/source/index.rst create mode 100644 doc/source/programmers-guide.rst create mode 100644 doc/source/qpack-howto.rst (limited to 'doc') diff --git a/doc/.gitignore b/doc/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/doc/.gitignore @@ -0,0 +1 @@ +/build diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 0000000..6ede162 --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,51 @@ +# nghttp3 + +# Copyright (c) 2020 nghttp3 contributors + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help html apiref + +apiref: mkapiref.py \ + $(top_builddir)/lib/includes/nghttp3/version.h \ + $(top_srcdir)/lib/includes/nghttp3/nghttp3.h + $(top_srcdir)/doc/mkapiref.py \ + source/apiref.rst source/macros.rst source/enums.rst source/types.rst \ + source $(wordlist 2, $(words $^), $^) + +html-local: apiref + @$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +clean-local: + -rm -rf "$(BUILDDIR)" diff --git a/doc/make.bat b/doc/make.bat new file mode 100644 index 0000000..6247f7e --- /dev/null +++ b/doc/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/doc/mkapiref.py b/doc/mkapiref.py new file mode 100755 index 0000000..71337a7 --- /dev/null +++ b/doc/mkapiref.py @@ -0,0 +1,358 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# nghttp2 - HTTP/2 C Library +# +# Copyright (c) 2020 nghttp3 contributors +# Copyright (c) 2020 ngtcp2 contributors +# Copyright (c) 2012 Tatsuhiro Tsujikawa +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# Generates API reference from C source code. + +import re, sys, argparse, os.path + +class FunctionDoc: + def __init__(self, name, content, domain, filename): + self.name = name + self.content = content + self.domain = domain + if self.domain == 'function': + self.funcname = re.search(r'(nghttp3_[^ )]+)\(', self.name).group(1) + self.filename = filename + + def write(self, out): + out.write('.. {}:: {}\n'.format(self.domain, self.name)) + out.write('\n') + for line in self.content: + out.write(' {}\n'.format(line)) + +class StructDoc: + def __init__(self, name, content, members, member_domain): + self.name = name + self.content = content + self.members = members + self.member_domain = member_domain + + def write(self, out): + if self.name: + out.write('.. type:: {}\n'.format(self.name)) + out.write('\n') + for line in self.content: + out.write(' {}\n'.format(line)) + out.write('\n') + for name, content in self.members: + out.write(' .. {}:: {}\n'.format(self.member_domain, name)) + out.write('\n') + for line in content: + out.write(' {}\n'.format(line)) + out.write('\n') + +class EnumDoc: + def __init__(self, name, content, members): + self.name = name + self.content = content + self.members = members + + def write(self, out): + if self.name: + out.write('.. type:: {}\n'.format(self.name)) + out.write('\n') + for line in self.content: + out.write(' {}\n'.format(line)) + out.write('\n') + for name, content in self.members: + out.write(' .. enum:: {}\n'.format(name)) + out.write('\n') + for line in content: + out.write(' {}\n'.format(line)) + out.write('\n') + +class MacroDoc: + def __init__(self, name, content): + self.name = name + self.content = content + + def write(self, out): + out.write('''.. macro:: {}\n'''.format(self.name)) + out.write('\n') + for line in self.content: + out.write(' {}\n'.format(line)) + +class MacroSectionDoc: + def __init__(self, content): + self.content = content + + def write(self, out): + out.write('\n') + c = ' '.join(self.content).strip() + out.write(c) + out.write('\n') + out.write('-' * len(c)) + out.write('\n\n') + +class TypedefDoc: + def __init__(self, name, content): + self.name = name + self.content = content + + def write(self, out): + out.write('''.. type:: {}\n'''.format(self.name)) + out.write('\n') + for line in self.content: + out.write(' {}\n'.format(line)) + +def make_api_ref(infile): + macros = [] + enums = [] + types = [] + functions = [] + while True: + line = infile.readline() + if not line: + break + elif line == '/**\n': + line = infile.readline() + doctype = line.split()[1] + if doctype == '@function': + functions.append(process_function('function', infile)) + elif doctype == '@functypedef': + types.append(process_function('type', infile)) + elif doctype == '@struct' or doctype == '@union': + types.append(process_struct(infile)) + elif doctype == '@enum': + enums.append(process_enum(infile)) + elif doctype == '@macro': + macros.append(process_macro(infile)) + elif doctype == '@macrosection': + macros.append(process_macrosection(infile)) + elif doctype == '@typedef': + types.append(process_typedef(infile)) + return macros, enums, types, functions + +def output( + title, indexfile, macrosfile, enumsfile, typesfile, funcsdir, + macros, enums, types, functions): + indexfile.write(''' +{title} +{titledecoration} + +.. toctree:: + :maxdepth: 1 + + {macros} + {enums} + {types} +'''.format( + title=title, titledecoration='='*len(title), + macros=os.path.splitext(os.path.basename(macrosfile.name))[0], + enums=os.path.splitext(os.path.basename(enumsfile.name))[0], + types=os.path.splitext(os.path.basename(typesfile.name))[0], +)) + + for doc in functions: + indexfile.write(' {}\n'.format(doc.funcname)) + + macrosfile.write(''' +Macros +====== +''') + for doc in macros: + doc.write(macrosfile) + + enumsfile.write(''' +Enums +===== +''') + for doc in enums: + doc.write(enumsfile) + + typesfile.write(''' +Types (structs, unions and typedefs) +==================================== +''') + for doc in types: + doc.write(typesfile) + + for doc in functions: + with open(os.path.join(funcsdir, doc.funcname + '.rst'), 'w') as f: + f.write(''' +{funcname} +{secul} + +Synopsis +-------- + +*#include * + +'''.format(funcname=doc.funcname, secul='='*len(doc.funcname), + filename=doc.filename)) + doc.write(f) + +def process_macro(infile): + content = read_content(infile) + lines = [] + while True: + line = infile.readline() + if not line: + break + line = line.rstrip() + lines.append(line.rstrip('\\')) + if not line.endswith('\\'): + break + + macro_name = re.sub(r'#define ', '', ''.join(lines)) + m = re.match(r'^[^( ]+(:?\(.*?\))?', macro_name) + macro_name = m.group(0) + return MacroDoc(macro_name, content) + +def process_macrosection(infile): + content = read_content(infile) + return MacroSectionDoc(content) + +def process_typedef(infile): + content = read_content(infile) + typedef = infile.readline() + typedef = re.sub(r';\n$', '', typedef) + typedef = re.sub(r'typedef ', '', typedef) + return TypedefDoc(typedef, content) + +def process_enum(infile): + members = [] + enum_name = None + content = read_content(infile) + while True: + line = infile.readline() + if not line: + break + elif re.match(r'\s*/\*\*\n', line): + member_content = read_content(infile) + line = infile.readline() + items = line.split() + member_name = items[0].rstrip(',') + if len(items) >= 3: + member_content.insert(0, '(``{}``) '\ + .format(' '.join(items[2:]).rstrip(','))) + members.append((member_name, member_content)) + elif line.startswith('}'): + enum_name = line.rstrip().split()[1] + enum_name = re.sub(r';$', '', enum_name) + break + return EnumDoc(enum_name, content, members) + +def process_struct(infile): + members = [] + struct_name = None + content = read_content(infile) + while True: + line = infile.readline() + if not line: + break + elif re.match(r'\s*/\*\*\n', line): + member_content = read_content(infile) + line = infile.readline() + member_name = line.rstrip().rstrip(';') + members.append((member_name, member_content)) + elif line.startswith('}') or\ + (line.startswith('typedef ') and line.endswith(';\n')): + if line.startswith('}'): + index = 1 + else: + index = 3 + struct_name = line.rstrip().split()[index] + struct_name = re.sub(r';$', '', struct_name) + break + return StructDoc(struct_name, content, members, 'member') + +def process_function(domain, infile): + content = read_content(infile) + func_proto = [] + while True: + line = infile.readline() + if not line: + break + elif line == '\n': + break + else: + func_proto.append(line) + func_proto = ''.join(func_proto) + func_proto = re.sub(r'int (settings|callbacks)_version,', + '', func_proto) + func_proto = re.sub(r'_versioned\(', '(', func_proto) + func_proto = re.sub(r';\n$', '', func_proto) + func_proto = re.sub(r'\s+', ' ', func_proto) + func_proto = re.sub(r'NGHTTP3_EXTERN ', '', func_proto) + func_proto = re.sub(r'typedef ', '', func_proto) + filename = os.path.basename(infile.name) + return FunctionDoc(func_proto, content, domain, filename) + +def read_content(infile): + content = [] + while True: + line = infile.readline() + if not line: + break + if re.match(r'\s*\*/\n', line): + break + else: + content.append(transform_content(line.rstrip())) + return content + +def arg_repl(matchobj): + return '*{}*'.format(matchobj.group(1).replace('*', '\\*')) + +def transform_content(content): + content = re.sub(r'^\s+\* ?', '', content) + content = re.sub(r'\|([^\s|]+)\|', arg_repl, content) + content = re.sub(r':enum:', ':macro:', content) + return content + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="Generate API reference") + parser.add_argument('--title', default='API Reference', + help='title of index page') + parser.add_argument('index', type=argparse.FileType('w'), + help='index output file') + parser.add_argument('macros', type=argparse.FileType('w'), + help='macros section output file. The filename should be macros.rst') + parser.add_argument('enums', type=argparse.FileType('w'), + help='enums section output file. The filename should be enums.rst') + parser.add_argument('types', type=argparse.FileType('w'), + help='types section output file. The filename should be types.rst') + parser.add_argument('funcsdir', + help='functions doc output dir') + parser.add_argument('files', nargs='+', type=argparse.FileType('r'), + help='source file') + args = parser.parse_args() + macros = [] + enums = [] + types = [] + funcs = [] + for infile in args.files: + m, e, t, f = make_api_ref(infile) + macros.extend(m) + enums.extend(e) + types.extend(t) + funcs.extend(f) + funcs.sort(key=lambda x: x.funcname) + output( + args.title, + args.index, args.macros, args.enums, args.types, args.funcsdir, + macros, enums, types, funcs) diff --git a/doc/source/.gitignore b/doc/source/.gitignore new file mode 100644 index 0000000..a1a2e88 --- /dev/null +++ b/doc/source/.gitignore @@ -0,0 +1,5 @@ +/conf.py +/apiref.rst +/enums.rst +/macros.rst +/types.rst diff --git a/doc/source/conf.py.in b/doc/source/conf.py.in new file mode 100644 index 0000000..171595f --- /dev/null +++ b/doc/source/conf.py.in @@ -0,0 +1,94 @@ +# nghttp3 + +# Copyright (c) 2020 nghttp3 contributors + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- Project information ----------------------------------------------------- + +project = 'nghttp3' +copyright = '2020, nghttp3 contributors' +author = 'nghttp3 contributors' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# The reST default role (used for this markup: `text`) to use for all documents. +default_role = 'c:func' +primary_domain = 'c' + +# manpage URL pattern +manpages_url = 'https://man7.org/linux/man-pages/man{section}/{page}.{section}.html' + +# The default language to highlight source code in. +highlight_language = 'c' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '@PACKAGE_VERSION@' +# The full version, including alpha/beta/rc tags. +release = '@PACKAGE_VERSION@' diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 0000000..2ba7331 --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,22 @@ +.. nghttp3 documentation master file, created by + sphinx-quickstart on Mon Nov 30 22:15:12 2020. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to nghttp3's documentation! +=================================== + +.. toctree:: + :maxdepth: 1 + :caption: Contents: + + programmers-guide + qpack-howto + apiref + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/doc/source/programmers-guide.rst b/doc/source/programmers-guide.rst new file mode 100644 index 0000000..d47d19c --- /dev/null +++ b/doc/source/programmers-guide.rst @@ -0,0 +1,207 @@ +The nghttp3 programmers' guide +============================== + +This document describes a basic usage of nghttp3 library and common +pitfalls which programmers might encounter. + +Assumptions +----------- + +nghttp3 is a thin HTTP/3 layer over an underlying QUIC stack. It +relies on an underlying QUIC stack for flow control and connection +management. Although nghttp3 is QUIC stack agnostic, it expects some +particular interfaces from QUIC stack. We will describe them below. + +QPACK operations are done behind the scenes. Application can use +:type:`nghttp3_settings` to change the behaviour of QPACK +encoder/decoder. + +We define some keywords to avoid ambiguity in this document: + +* HTTP payload: HTTP request/response body +* HTTP stream data: Series of HTTP header fields, HTTP payload, and + HTTP trailer fields, serialized into HTTP/3 wire format, which is + passed to or received from QUIC stack. + +Initialization +-------------- + +The :type:`nghttp3_conn` is a basic building block of nghttp3 library. +It is created per HTTP/3 connection. If an endpoint is a client, use +`nghttp3_conn_client_new` to initialize it as client. If it is a +server, use `nghttp3_conn_server_new` to initialize it as server. + +Those initialization functions take :type:`nghttp3_callbacks`. All +callbacks are optional, but setting no callback functions makes +nghttp3 library useless for the most cases. We list callbacks which +effectively required to do HTTP/3 transaction below: + +* :member:`acked_stream_data `: + Application has to retain HTTP payload (HTTP request/response body) + until they are no longer used by :type:`nghttp3_conn`. This + callback functions tells the largest offset of HTTP payload + acknowledged by a remote endpoint, and no longer used. +* :member:`stream_close `: It is + called when a stream is closed. It is useful to free resources + allocated for a stream. +* :member:`recv_data `: It is called when + HTTP payload (HTTP request/response body) is received. +* :member:`deferred_consume `: It + is called when :type:`nghttp3_conn` consumed HTTP stream data which + had been blocked for synchronization between streams. Application + has to tell QUIC stack the number of bytes consumed which affects + flow control. We will discuss more about this callback later when + explaining `nghttp3_conn_read_stream`. +* :member:`recv_header `: It is called + when an HTTP header field is received. +* :member:`send_stop_sending `: + It is called when QUIC STOP_SENDING frame must be sent for a + particular stream. Sending STOP_SENDING frame means that + :type:`nghttp3_conn` no longer reads an incoming data for a + particular stream. Application has to tell QUIC stack to send + STOP_SENDING frame. +* :member:`reset_stream `: It is + called when QUIC RESET_STREAM frame must be sent for a particular + stream. Sending RESET_STREAM frame means that :type:`nghttp3_conn` + stops sending any HTTP stream data to a particular stream. + Application has to tell QUIC stack to send RESET_STREAM frame. + +The initialization functions also takes :type:`nghttp3_settings` which +is a set of options to tweak HTTP3/ connection settings. +`nghttp3_settings_default` fills the default values. + +The *user_data* parameter to the initialization function is an opaque +pointer and it is passed to callback functions. + +Binding control streams +----------------------- + +HTTP/3 requires at least 3 local unidirectional streams for a control +stream and QPACK encoder/decoder streams. + +Use the following functions to bind those streams to their purposes: + +* `nghttp3_conn_bind_control_stream`: Bind a given stream ID to a HTTP + control stream. +* `nghttp3_conn_bind_qpack_streams`: Bind given 2 stream IDs to QPACK + encoder and decoder streams. + +Reading HTTP stream data +------------------------ + +`nghttp3_conn_read_stream` reads HTTP stream data from a particular +stream. It returns the number of bytes "consumed". "Consumed" means +that the those bytes are completely processed and QUIC stack can +increase the flow control credit of both stream and connection by that +amount. + +The HTTP payload notified by :member:`nghttp3_callbacks.recv_data` is +not included in the return value. This is because the consumption of +those data is done by application and nghttp3 library does not know +when that happens. + +Some HTTP stream data might be consumed later because of +synchronization between streams. In this case, those bytes are +notified by :member:`nghttp3_callbacks.deferred_consume`. + +In every case, the number of consumed HTTP stream data must be +notified to QUIC stack so that it can extend flow control limits. + +Writing HTTP stream data +------------------------ + +`nghttp3_conn_writev_stream` writes HTTP stream data to a particular +stream. The order of streams to produce HTTP stream data is +determined by the nghttp3 library. In general, the control streams +have higher priority. The regular HTTP streams are ordered by +header-based HTTP priority (see +https://tools.ietf.org/html/draft-ietf-httpbis-priority-03). + +When HTTP stream data is generated, its stream ID is assigned to +*\*pstream_id*. The pointer to HTTP stream data is assigned to *vec*, +and the function returns the number of *vec* it filled. If the +generated data is the final part of the stream, *\*pfin* gets nonzero +value. If no HTTP stream data is generated, the function returns 0 +and *\*pstream_id* gets -1. + +The function might return 0 and *\*pstream_id* has proper stream ID +and *\*pfin* set to nonzero. In this case, no data is written, but it +signals the end of the stream. Even though no data is written, QUIC +stack should be notified of the end of the stream. + +The produced HTTP stream data is passed to QUIC stack. Then call +`nghttp3_conn_add_write_offset` with the number of bytes accepted by +QUIC stack. This must be done even when the written data is 0 bytes +with fin (refer to the previous paragraph for this corner case). + +If QUIC stack indicates that a stream is blocked by stream level flow +control limit, call `nghttp3_conn_block_stream`. It makes the library +not to generate HTTP stream data for the stream. Call +`nghttp3_conn_unblock_stream` when stream level flow control limit is +increased. + +If QUIC stack indicates that the write side of stream is closed, call +`nghttp3_conn_shutdown_stream_write` instead of +`nghttp3_conn_block_stream` so that the stream never be scheduled in +the future. + +Creating HTTP request or response +--------------------------------- + +In order to create HTTP request, client application calls +`nghttp3_conn_submit_request`. :type:`nghttp3_data_reader` is used to +send HTTP payload (HTTP request body). + +Similarly, server application calls `nghttp3_conn_submit_response` to +create HTTP response. :type:`nghttp3_data_reader` is also used to +send HTTP payload (HTTP response body). + +In both cases, if :type:`nghttp3_data_reader` is not provided, no HTTP +payload is generated. + +The :member:`nghttp3_data_reader.read_data` is a callback function to +generate HTTP payload. Application must retain the data passed to the +library until those data are acknowledged by +:member:`nghttp3_callbacks.acked_stream_data`. When no data is +available but will become available in the future, application returns +:macro:`NGHTTP3_ERR_WOULDBLOCK` from this callback. Then the callback +is not called for the particular stream until +`nghttp3_conn_resume_stream` is called. + +Reading HTTP request or response +-------------------------------- + +The :member:`nghttp3_callbacks.recv_header` is called when an HTTP +header field is received. + +The :member:`nghttp3_callbacks.recv_data` is called when HTTP payload +is received. + +Acknowledgement of HTTP stream data +----------------------------------- + +QUIC stack must provide an interface to notify the amount of data +acknowledged by a remote endpoint. `nghttp3_conn_add_ack_offset` must +be called with the largest offset of acknowledged HTTP stream data. + +Handling QUIC stream events +--------------------------- + +If underlying QUIC stream is closed, call `nghttp3_conn_close_stream`. + +If underlying QUIC stream is reset by a remote endpoint (that is when +RESET_STREAM is received) or no longer read by a local endpoint (that +is when STOP_SENDING is sent), call +`nghttp3_conn_shutdown_stream_read`. + +Closing HTTP/3 connection gracefully +------------------------------------ + +`nghttp3_conn_submit_shutdown_notice` creates a message to a remote +endpoint that HTTP/3 connection is going down. The receiving endpoint +should stop sending HTTP request after reading this signal. After a +couple of RTTs, call `nghttp3_conn_submit_shutdown` to start graceful +shutdown. After calling this function, the local endpoint starts +rejecting new incoming streams. The existing streams are processed +normally. When all those streams are completely processed, the +connection can be closed. diff --git a/doc/source/qpack-howto.rst b/doc/source/qpack-howto.rst new file mode 100644 index 0000000..a38d285 --- /dev/null +++ b/doc/source/qpack-howto.rst @@ -0,0 +1,85 @@ +QPACK How-To +============ + +Using QPACK encoder +------------------- + +Firstly, create QPACK encoder by calling `nghttp3_qpack_encoder_new`. +It requires *hard_max_dtable_size* parameter. When in doubt, pass +4096 for this tutorial. Optionally, call +`nghttp3_qpack_encoder_set_max_dtable_capacity` to set the maximum +size of dynamic table. You can also call +`nghttp3_qpack_encoder_set_max_blocked_streams` to set the maximum +number of streams that can be blocked. + +In order to encode HTTP header fields, they must be stored in an array +of :type:`nghttp3_nv`. Then call `nghttp3_qpack_encoder_encode`. It +writes 3 buffers; *pbuf*, *rbuf*, and *ebuf*. They are a header block +prefix, request stream, and encoder stream respectively. A header +block prefix and request stream must be sent in this order to a stream +denoted by *stream_id* passed to the function. Encoder stream must be +sent to the encoder stream you setup. + +In order to read decoder stream, call +`nghttp3_qpack_encoder_read_decoder`. + +Once QPACK encoder is no longer used, call `nghttp3_qpack_encoder_del` +to free up memory allocated for it. + +Using QPACK decoder +------------------- + +`nghttp3_qpack_decoder_new` will create new QPACK decoder. It +requires *hard_max_dtable_size* and *max_blocked* parameters. When in +doubt, pass 4096 and 0 respectively for this tutorial. + +In order to read encoder stream, call +`nghttp3_qpack_decoder_read_encoder`. This might update dynamic +table, but does not emit any header fields. + +In order to read request stream, call +`nghttp3_qpack_decoder_read_request`. This is the function to emit +header fields. *sctx* stores a per-stream decoder state and must be +created by `nghttp3_qpack_stream_context_new`. It identifies a single +encoded header block in a particular request stream. *fin* must be +nonzero if and only if a passed data contains the last part of encoded +header block. + +The scope of :type:`nghttp3_qpack_stream_context` is per header block, +but `nghttp3_qpack_stream_context_reset` resets its state and can be +reused for an another header block in the same stream. In general, +you can reset it when you see that +:macro:`NGHTTP3_QPACK_DECODE_FLAG_FINAL` is set in *\*pflags*. When +:type:`nghttp3_qpack_stream_context` is no longer necessary, call +`nghttp3_qpack_stream_context_del` to free up its resource. + +`nghttp3_qpack_decoder_read_request` succeeds, *\*pflags* is assigned. +If it has :macro:`NGHTTP3_QPACK_DECODE_FLAG_EMIT` set, a header field +is emitted and stored in the buffer pointed by *nv*. If *\*pflags* +has :macro:`NGHTTP3_QPACK_DECODE_FLAG_FINAL` set, all header fields +have been successfully decoded. If *\*pflags* has +:macro:`NGHTTP3_QPACK_DECODE_FLAG_BLOCKED` set, decoding is blocked +due to required insert count, which means that more data must be read +by `nghttp3_qpack_decoder_read_encoder`. + +`nghttp3_qpack_decoder_read_request` returns the number of bytes read. +When a header field is emitted, it might read data partially. +Applciation has to call the function repeatedly by adjusting the +pointer to data and its length until the function consumes all data or +:macro:`NGHTTP3_QPACK_DECODE_FLAG_BLOCKED` is set in *\*pflags*. + +If *nv* is assigned, its :member:`nv->name ` +and :member:`nv->value ` are reference counted +and already incremented by 1. If application finishes processing +these values, it must call `nghttp3_rcbuf_decref(nv->name) +` and `nghttp3_rcbuf_decref(nv->value) +`. + +If an application has no interest to decode header fields for a +particular stream, call `nghttp3_qpack_decoder_cancel_stream`. + +In order to tell decoding state to an encoder, QPACK decoder has to +write decoder stream by calling `nghttp3_qpack_decoder_write_decoder`. + +Once QPACK decoder is no longer used, call `nghttp3_qpack_decoder_del` +to free up memory allocated for it. -- cgit v1.2.3