mirror of
https://github.com/nghttp2/nghttp2.git
synced 2025-12-08 11:08:52 +08:00
Compare commits
109 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f12f0671c | ||
|
|
39fb5972d4 | ||
|
|
063e1d7606 | ||
|
|
57aee184ca | ||
|
|
4ae2850aa6 | ||
|
|
f586797f7d | ||
|
|
0d8c8ca033 | ||
|
|
5ea90ba6bd | ||
|
|
fd8067be94 | ||
|
|
ad34b45782 | ||
|
|
d0f96da2c0 | ||
|
|
da57989cf6 | ||
|
|
f086b7b67e | ||
|
|
471fd688b9 | ||
|
|
02adaac368 | ||
|
|
1bbef4be74 | ||
|
|
918f8cca36 | ||
|
|
22b39f0337 | ||
|
|
e282462e1f | ||
|
|
e4843201b0 | ||
|
|
753f6d4a3e | ||
|
|
aba2dbddc2 | ||
|
|
510dff4c39 | ||
|
|
e260006182 | ||
|
|
7d481db248 | ||
|
|
4f52f60b3c | ||
|
|
5afc25623e | ||
|
|
3ce1c1d39f | ||
|
|
21a3edfc60 | ||
|
|
026ab797eb | ||
|
|
79945c0c45 | ||
|
|
f8c1da7f3c | ||
|
|
a1bb48770c | ||
|
|
d05b77b36c | ||
|
|
ce53bd239e | ||
|
|
39287314d3 | ||
|
|
231db4a3e6 | ||
|
|
c30d252f94 | ||
|
|
6d46249b7b | ||
|
|
1b63e6d478 | ||
|
|
28defbfb4a | ||
|
|
75b98662be | ||
|
|
1df682140c | ||
|
|
7273c7d688 | ||
|
|
ef1595672c | ||
|
|
98c959291f | ||
|
|
2135c87f94 | ||
|
|
e1106e0f91 | ||
|
|
7b0113ac77 | ||
|
|
6f0c88580f | ||
|
|
18064d1626 | ||
|
|
3029e5c530 | ||
|
|
be77b47ab5 | ||
|
|
77958eab46 | ||
|
|
8f41accf41 | ||
|
|
e19d5efced | ||
|
|
69c3e2114a | ||
|
|
6dcfe1c3f8 | ||
|
|
d076d54f67 | ||
|
|
aba7e9e7f9 | ||
|
|
4ac7152f94 | ||
|
|
d348ea3384 | ||
|
|
b4709b9e8c | ||
|
|
21e1af2ae7 | ||
|
|
226a09b04a | ||
|
|
587f37a597 | ||
|
|
97f488a5c7 | ||
|
|
45e0d42c51 | ||
|
|
bddc4a0a04 | ||
|
|
a9338f1c0e | ||
|
|
4ad00200a2 | ||
|
|
6b38f7e0d2 | ||
|
|
4872c9b4c9 | ||
|
|
d8ca0a8d47 | ||
|
|
2222a898a1 | ||
|
|
9e685a2734 | ||
|
|
c86e839091 | ||
|
|
d722a09581 | ||
|
|
5967667e9e | ||
|
|
d044c58558 | ||
|
|
200217d8ea | ||
|
|
02bb2c3e83 | ||
|
|
64c754e2c5 | ||
|
|
ad65121496 | ||
|
|
3235b88253 | ||
|
|
5e3ab6da5a | ||
|
|
6f9c9f8518 | ||
|
|
65f2b16132 | ||
|
|
05a761b628 | ||
|
|
14f5240d8f | ||
|
|
baadec5ef4 | ||
|
|
d20229d9b9 | ||
|
|
e06af02573 | ||
|
|
5cd34920b7 | ||
|
|
e436caf0bb | ||
|
|
f0d41a5ac2 | ||
|
|
068529586d | ||
|
|
45cdb10c46 | ||
|
|
2170d958d5 | ||
|
|
9d78691936 | ||
|
|
1508c50a45 | ||
|
|
dd0a72579f | ||
|
|
e7bc269e97 | ||
|
|
e2557059e5 | ||
|
|
0c4e26fd24 | ||
|
|
3002674bac | ||
|
|
e1f7643c92 | ||
|
|
d70eb14ce0 | ||
|
|
31c19cbda4 |
15
.gitignore
vendored
15
.gitignore
vendored
@@ -6,6 +6,7 @@
|
||||
*.lo
|
||||
*.m4
|
||||
*.o
|
||||
*.pyc
|
||||
.deps/
|
||||
.libs/
|
||||
INSTALL
|
||||
@@ -31,10 +32,10 @@ test-driver
|
||||
# test logs generated by `make check`
|
||||
*.log
|
||||
*.trs
|
||||
|
||||
lib/MSVC_obj/
|
||||
_VC_ROOT/
|
||||
.depend.MSVC
|
||||
*.pyd
|
||||
*.egg-info/
|
||||
python/nghttp2.c
|
||||
|
||||
lib/MSVC_obj/
|
||||
_VC_ROOT/
|
||||
.depend.MSVC
|
||||
*.pyd
|
||||
*.egg-info/
|
||||
python/nghttp2.c
|
||||
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "third-party/mruby"]
|
||||
path = third-party/mruby
|
||||
url = https://github.com/mruby/mruby
|
||||
@@ -31,7 +31,8 @@ before_script:
|
||||
- autoreconf -i
|
||||
- automake
|
||||
- autoconf
|
||||
- ./configure --enable-werror
|
||||
- git submodule update --init
|
||||
- ./configure --enable-werror --with-mruby
|
||||
script:
|
||||
- make
|
||||
- make check
|
||||
|
||||
20
README.rst
20
README.rst
@@ -113,6 +113,20 @@ If you are using Ubuntu 14.04 LTS (trusty), run the following to install the nee
|
||||
spdylay is not packaged in Ubuntu, so you need to build it yourself:
|
||||
http://tatsuhiro-t.github.io/spdylay/
|
||||
|
||||
To enable mruby support for nghttpx, `mruby
|
||||
<https://github.com/mruby/mruby>`_ is required. We need to build
|
||||
mruby with C++ ABI explicitly turned on, and probably need other
|
||||
mrgems, mruby is manged by git submodule under third-party/mruby
|
||||
directory. Currently, mruby support for nghttpx is disabled by
|
||||
default. To enable mruby support, use ``--with-mruby`` configure
|
||||
option. Note that at the time of this writing, libmruby-dev and mruby
|
||||
packages in Debian/Ubuntu are not usable for nghttp2, since they do
|
||||
not enable C++ ABI. To build mruby, the following packages are
|
||||
required:
|
||||
|
||||
* ruby
|
||||
* bison
|
||||
|
||||
Building from git
|
||||
-----------------
|
||||
|
||||
@@ -127,6 +141,12 @@ used::
|
||||
|
||||
To compile the source code, gcc >= 4.8.3 or clang >= 3.4 is required.
|
||||
|
||||
.. note::
|
||||
|
||||
To enable mruby support in nghttpx, run ``git submodule update
|
||||
--init`` before running configure script, and use ``--with-mruby``
|
||||
configure option.
|
||||
|
||||
.. note::
|
||||
|
||||
Mac OS X users may need the ``--disable-threads`` configure option to
|
||||
|
||||
25
configure.ac
25
configure.ac
@@ -25,7 +25,7 @@ dnl Do not change user variables!
|
||||
dnl http://www.gnu.org/software/automake/manual/html_node/Flag-Variables-Ordering.html
|
||||
|
||||
AC_PREREQ(2.61)
|
||||
AC_INIT([nghttp2], [1.3.0], [t-tujikawa@users.sourceforge.net])
|
||||
AC_INIT([nghttp2], [1.3.1], [t-tujikawa@users.sourceforge.net])
|
||||
AC_CONFIG_AUX_DIR([.])
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
AC_CONFIG_HEADERS([config.h])
|
||||
@@ -47,7 +47,7 @@ m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
|
||||
dnl See versioning rule:
|
||||
dnl http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
|
||||
AC_SUBST(LT_CURRENT, 15)
|
||||
AC_SUBST(LT_REVISION, 0)
|
||||
AC_SUBST(LT_REVISION, 1)
|
||||
AC_SUBST(LT_AGE, 1)
|
||||
|
||||
major=`echo $PACKAGE_VERSION |cut -d. -f1 | sed -e "s/[^0-9]//g"`
|
||||
@@ -119,6 +119,11 @@ AC_ARG_WITH([spdylay],
|
||||
[Use spdylay [default=check]])],
|
||||
[request_spdylay=$withval], [request_spdylay=check])
|
||||
|
||||
AC_ARG_WITH([mruby],
|
||||
[AS_HELP_STRING([--with-mruby],
|
||||
[Use mruby [default=no]])],
|
||||
[request_mruby=$withval], [request_mruby=no])
|
||||
|
||||
AC_ARG_WITH([cython],
|
||||
[AS_HELP_STRING([--with-cython=PATH],
|
||||
[Use cython in given PATH])],
|
||||
@@ -370,6 +375,20 @@ fi
|
||||
|
||||
AM_CONDITIONAL([HAVE_SPDYLAY], [ test "x${have_spdylay}" = "xyes" ])
|
||||
|
||||
# mruby (for src/nghttpx)
|
||||
have_mruby=no
|
||||
if test "x${request_mruby}" = "xyes"; then
|
||||
# We are going to build mruby
|
||||
have_mruby=yes
|
||||
AC_DEFINE([HAVE_MRUBY], [1], [Define to 1 if you have `mruby` library.])
|
||||
LIBMRUBY_LIBS="-lmruby -lm"
|
||||
LIBMRUBY_CFLAGS=
|
||||
AC_SUBST([LIBMRUBY_LIBS])
|
||||
AC_SUBST([LIBMRUBY_CFLAGS])
|
||||
fi
|
||||
|
||||
AM_CONDITIONAL([HAVE_MRUBY], [test "x${have_mruby}" = "xyes"])
|
||||
|
||||
# Check Boost Asio library
|
||||
have_asio_lib=no
|
||||
|
||||
@@ -450,6 +469,7 @@ AM_CONDITIONAL([ENABLE_EXAMPLES], [ test "x${enable_examples}" = "xyes" ])
|
||||
enable_third_party=no
|
||||
if test "x${enable_examples}" = "xyes" ||
|
||||
test "x${enable_app}" = "xyes" ||
|
||||
test "x${enable_hpack_tools}" = "xyes" ||
|
||||
test "x${enable_asio_lib}" = "xyes"; then
|
||||
enable_third_party=yes
|
||||
fi
|
||||
@@ -717,6 +737,7 @@ AC_MSG_NOTICE([summary of build options:
|
||||
Libev: ${have_libev}
|
||||
Libevent(SSL): ${have_libevent_openssl}
|
||||
Spdylay: ${have_spdylay}
|
||||
MRuby: ${have_mruby}
|
||||
Jansson: ${have_jansson}
|
||||
Jemalloc: ${have_jemalloc}
|
||||
Zlib: ${have_zlib}
|
||||
|
||||
2
contrib/nghttpx-init.in
Normal file → Executable file
2
contrib/nghttpx-init.in
Normal file → Executable file
@@ -20,7 +20,7 @@ NAME=nghttpx
|
||||
# Depending on the configuration, binary may be located under @sbindir@
|
||||
DAEMON=@bindir@/$NAME
|
||||
PIDFILE=/var/run/$NAME.pid
|
||||
DAEMON_ARGS="--conf /etc/nghttpx/nghttpx.conf --pid-file=$PIDFILE"
|
||||
DAEMON_ARGS="--conf /etc/nghttpx/nghttpx.conf --pid-file=$PIDFILE --daemon"
|
||||
SCRIPTNAME=/etc/init.d/$NAME
|
||||
|
||||
# Exit if the package is not installed
|
||||
|
||||
@@ -151,6 +151,9 @@ EXTRA_DIST = \
|
||||
sources/python-apiref.rst \
|
||||
sources/building-android-binary.rst \
|
||||
sources/contribute.rst \
|
||||
_exts/sphinxcontrib/LICENSE.rubydomain \
|
||||
_exts/sphinxcontrib/__init__.py \
|
||||
_exts/sphinxcontrib/rubydomain.py \
|
||||
_themes/sphinx_rtd_theme/__init__.py \
|
||||
_themes/sphinx_rtd_theme/breadcrumbs.html \
|
||||
_themes/sphinx_rtd_theme/footer.html \
|
||||
|
||||
28
doc/_exts/sphinxcontrib/LICENSE.rubydomain
Normal file
28
doc/_exts/sphinxcontrib/LICENSE.rubydomain
Normal file
@@ -0,0 +1,28 @@
|
||||
If not otherwise noted, the extensions in this package are licensed
|
||||
under the following license.
|
||||
|
||||
Copyright (c) 2010 by the contributors (see AUTHORS file).
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
14
doc/_exts/sphinxcontrib/__init__.py
Normal file
14
doc/_exts/sphinxcontrib/__init__.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
sphinxcontrib
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
This package is a namespace package that contains all extensions
|
||||
distributed in the ``sphinx-contrib`` distribution.
|
||||
|
||||
:copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
__import__('pkg_resources').declare_namespace(__name__)
|
||||
|
||||
695
doc/_exts/sphinxcontrib/rubydomain.py
Normal file
695
doc/_exts/sphinxcontrib/rubydomain.py
Normal file
@@ -0,0 +1,695 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
sphinx.domains.ruby
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The Ruby domain.
|
||||
|
||||
:copyright: Copyright 2010 by SHIBUKAWA Yoshiki
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.parsers.rst import directives
|
||||
|
||||
from sphinx import addnodes
|
||||
from sphinx.roles import XRefRole
|
||||
from sphinx.locale import l_, _
|
||||
from sphinx.domains import Domain, ObjType, Index
|
||||
from sphinx.directives import ObjectDescription
|
||||
from sphinx.util.nodes import make_refnode
|
||||
from sphinx.util.compat import Directive
|
||||
from sphinx.util.docfields import Field, GroupedField, TypedField
|
||||
|
||||
|
||||
# REs for Ruby signatures
|
||||
rb_sig_re = re.compile(
|
||||
r'''^ ([\w.]*\.)? # class name(s)
|
||||
(\$?\w+\??!?) \s* # thing name
|
||||
(?: \((.*)\) # optional: arguments
|
||||
(?:\s* -> \s* (.*))? # return annotation
|
||||
)? $ # and nothing more
|
||||
''', re.VERBOSE)
|
||||
|
||||
rb_paramlist_re = re.compile(r'([\[\],])') # split at '[', ']' and ','
|
||||
|
||||
separators = {
|
||||
'method':'#', 'attr_reader':'#', 'attr_writer':'#', 'attr_accessor':'#',
|
||||
'function':'.', 'classmethod':'.', 'class':'::', 'module':'::',
|
||||
'global':'', 'const':'::'}
|
||||
|
||||
rb_separator = re.compile(r"(?:\w+)?(?:::)?(?:\.)?(?:#)?")
|
||||
|
||||
|
||||
def _iteritems(d):
|
||||
|
||||
for k in d:
|
||||
yield k, d[k]
|
||||
|
||||
|
||||
def ruby_rsplit(fullname):
|
||||
items = [item for item in rb_separator.findall(fullname)]
|
||||
return ''.join(items[:-2]), items[-1]
|
||||
|
||||
|
||||
class RubyObject(ObjectDescription):
|
||||
"""
|
||||
Description of a general Ruby object.
|
||||
"""
|
||||
option_spec = {
|
||||
'noindex': directives.flag,
|
||||
'module': directives.unchanged,
|
||||
}
|
||||
|
||||
doc_field_types = [
|
||||
TypedField('parameter', label=l_('Parameters'),
|
||||
names=('param', 'parameter', 'arg', 'argument'),
|
||||
typerolename='obj', typenames=('paramtype', 'type')),
|
||||
TypedField('variable', label=l_('Variables'), rolename='obj',
|
||||
names=('var', 'ivar', 'cvar'),
|
||||
typerolename='obj', typenames=('vartype',)),
|
||||
GroupedField('exceptions', label=l_('Raises'), rolename='exc',
|
||||
names=('raises', 'raise', 'exception', 'except'),
|
||||
can_collapse=True),
|
||||
Field('returnvalue', label=l_('Returns'), has_arg=False,
|
||||
names=('returns', 'return')),
|
||||
Field('returntype', label=l_('Return type'), has_arg=False,
|
||||
names=('rtype',)),
|
||||
]
|
||||
|
||||
def get_signature_prefix(self, sig):
|
||||
"""
|
||||
May return a prefix to put before the object name in the signature.
|
||||
"""
|
||||
return ''
|
||||
|
||||
def needs_arglist(self):
|
||||
"""
|
||||
May return true if an empty argument list is to be generated even if
|
||||
the document contains none.
|
||||
"""
|
||||
return False
|
||||
|
||||
def handle_signature(self, sig, signode):
|
||||
"""
|
||||
Transform a Ruby signature into RST nodes.
|
||||
Returns (fully qualified name of the thing, classname if any).
|
||||
|
||||
If inside a class, the current class name is handled intelligently:
|
||||
* it is stripped from the displayed name if present
|
||||
* it is added to the full name (return value) if not present
|
||||
"""
|
||||
m = rb_sig_re.match(sig)
|
||||
if m is None:
|
||||
raise ValueError
|
||||
name_prefix, name, arglist, retann = m.groups()
|
||||
if not name_prefix:
|
||||
name_prefix = ""
|
||||
# determine module and class name (if applicable), as well as full name
|
||||
modname = self.options.get(
|
||||
'module', self.env.temp_data.get('rb:module'))
|
||||
classname = self.env.temp_data.get('rb:class')
|
||||
if self.objtype == 'global':
|
||||
add_module = False
|
||||
modname = None
|
||||
classname = None
|
||||
fullname = name
|
||||
elif classname:
|
||||
add_module = False
|
||||
if name_prefix and name_prefix.startswith(classname):
|
||||
fullname = name_prefix + name
|
||||
# class name is given again in the signature
|
||||
name_prefix = name_prefix[len(classname):].lstrip('.')
|
||||
else:
|
||||
separator = separators[self.objtype]
|
||||
fullname = classname + separator + name_prefix + name
|
||||
else:
|
||||
add_module = True
|
||||
if name_prefix:
|
||||
classname = name_prefix.rstrip('.')
|
||||
fullname = name_prefix + name
|
||||
else:
|
||||
classname = ''
|
||||
fullname = name
|
||||
|
||||
signode['module'] = modname
|
||||
signode['class'] = self.class_name = classname
|
||||
signode['fullname'] = fullname
|
||||
|
||||
sig_prefix = self.get_signature_prefix(sig)
|
||||
if sig_prefix:
|
||||
signode += addnodes.desc_annotation(sig_prefix, sig_prefix)
|
||||
|
||||
if name_prefix:
|
||||
signode += addnodes.desc_addname(name_prefix, name_prefix)
|
||||
# exceptions are a special case, since they are documented in the
|
||||
# 'exceptions' module.
|
||||
elif add_module and self.env.config.add_module_names:
|
||||
if self.objtype == 'global':
|
||||
nodetext = ''
|
||||
signode += addnodes.desc_addname(nodetext, nodetext)
|
||||
else:
|
||||
modname = self.options.get(
|
||||
'module', self.env.temp_data.get('rb:module'))
|
||||
if modname and modname != 'exceptions':
|
||||
nodetext = modname + separators[self.objtype]
|
||||
signode += addnodes.desc_addname(nodetext, nodetext)
|
||||
|
||||
signode += addnodes.desc_name(name, name)
|
||||
if not arglist:
|
||||
if self.needs_arglist():
|
||||
# for callables, add an empty parameter list
|
||||
signode += addnodes.desc_parameterlist()
|
||||
if retann:
|
||||
signode += addnodes.desc_returns(retann, retann)
|
||||
return fullname, name_prefix
|
||||
signode += addnodes.desc_parameterlist()
|
||||
|
||||
stack = [signode[-1]]
|
||||
for token in rb_paramlist_re.split(arglist):
|
||||
if token == '[':
|
||||
opt = addnodes.desc_optional()
|
||||
stack[-1] += opt
|
||||
stack.append(opt)
|
||||
elif token == ']':
|
||||
try:
|
||||
stack.pop()
|
||||
except IndexError:
|
||||
raise ValueError
|
||||
elif not token or token == ',' or token.isspace():
|
||||
pass
|
||||
else:
|
||||
token = token.strip()
|
||||
stack[-1] += addnodes.desc_parameter(token, token)
|
||||
if len(stack) != 1:
|
||||
raise ValueError
|
||||
if retann:
|
||||
signode += addnodes.desc_returns(retann, retann)
|
||||
return fullname, name_prefix
|
||||
|
||||
def get_index_text(self, modname, name):
|
||||
"""
|
||||
Return the text for the index entry of the object.
|
||||
"""
|
||||
raise NotImplementedError('must be implemented in subclasses')
|
||||
|
||||
def _is_class_member(self):
|
||||
return self.objtype.endswith('method') or self.objtype.startswith('attr')
|
||||
|
||||
def add_target_and_index(self, name_cls, sig, signode):
|
||||
if self.objtype == 'global':
|
||||
modname = ''
|
||||
else:
|
||||
modname = self.options.get(
|
||||
'module', self.env.temp_data.get('rb:module'))
|
||||
separator = separators[self.objtype]
|
||||
if self._is_class_member():
|
||||
if signode['class']:
|
||||
prefix = modname and modname + '::' or ''
|
||||
else:
|
||||
prefix = modname and modname + separator or ''
|
||||
else:
|
||||
prefix = modname and modname + separator or ''
|
||||
fullname = prefix + name_cls[0]
|
||||
# note target
|
||||
if fullname not in self.state.document.ids:
|
||||
signode['names'].append(fullname)
|
||||
signode['ids'].append(fullname)
|
||||
signode['first'] = (not self.names)
|
||||
self.state.document.note_explicit_target(signode)
|
||||
objects = self.env.domaindata['rb']['objects']
|
||||
if fullname in objects:
|
||||
self.env.warn(
|
||||
self.env.docname,
|
||||
'duplicate object description of %s, ' % fullname +
|
||||
'other instance in ' +
|
||||
self.env.doc2path(objects[fullname][0]),
|
||||
self.lineno)
|
||||
objects[fullname] = (self.env.docname, self.objtype)
|
||||
|
||||
indextext = self.get_index_text(modname, name_cls)
|
||||
if indextext:
|
||||
self.indexnode['entries'].append(('single', indextext,
|
||||
fullname, fullname))
|
||||
|
||||
def before_content(self):
|
||||
# needed for automatic qualification of members (reset in subclasses)
|
||||
self.clsname_set = False
|
||||
|
||||
def after_content(self):
|
||||
if self.clsname_set:
|
||||
self.env.temp_data['rb:class'] = None
|
||||
|
||||
|
||||
class RubyModulelevel(RubyObject):
|
||||
"""
|
||||
Description of an object on module level (functions, data).
|
||||
"""
|
||||
|
||||
def needs_arglist(self):
|
||||
return self.objtype == 'function'
|
||||
|
||||
def get_index_text(self, modname, name_cls):
|
||||
if self.objtype == 'function':
|
||||
if not modname:
|
||||
return _('%s() (global function)') % name_cls[0]
|
||||
return _('%s() (module function in %s)') % (name_cls[0], modname)
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
class RubyGloballevel(RubyObject):
|
||||
"""
|
||||
Description of an object on module level (functions, data).
|
||||
"""
|
||||
|
||||
def get_index_text(self, modname, name_cls):
|
||||
if self.objtype == 'global':
|
||||
return _('%s (global variable)') % name_cls[0]
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
class RubyEverywhere(RubyObject):
|
||||
"""
|
||||
Description of a class member (methods, attributes).
|
||||
"""
|
||||
|
||||
def needs_arglist(self):
|
||||
return self.objtype == 'method'
|
||||
|
||||
def get_index_text(self, modname, name_cls):
|
||||
name, cls = name_cls
|
||||
add_modules = self.env.config.add_module_names
|
||||
if self.objtype == 'method':
|
||||
try:
|
||||
clsname, methname = ruby_rsplit(name)
|
||||
except ValueError:
|
||||
if modname:
|
||||
return _('%s() (in module %s)') % (name, modname)
|
||||
else:
|
||||
return '%s()' % name
|
||||
if modname and add_modules:
|
||||
return _('%s() (%s::%s method)') % (methname, modname,
|
||||
clsname)
|
||||
else:
|
||||
return _('%s() (%s method)') % (methname, clsname)
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
class RubyClasslike(RubyObject):
|
||||
"""
|
||||
Description of a class-like object (classes, exceptions).
|
||||
"""
|
||||
|
||||
def get_signature_prefix(self, sig):
|
||||
return self.objtype + ' '
|
||||
|
||||
def get_index_text(self, modname, name_cls):
|
||||
if self.objtype == 'class':
|
||||
if not modname:
|
||||
return _('%s (class)') % name_cls[0]
|
||||
return _('%s (class in %s)') % (name_cls[0], modname)
|
||||
elif self.objtype == 'exception':
|
||||
return name_cls[0]
|
||||
else:
|
||||
return ''
|
||||
|
||||
def before_content(self):
|
||||
RubyObject.before_content(self)
|
||||
if self.names:
|
||||
self.env.temp_data['rb:class'] = self.names[0][0]
|
||||
self.clsname_set = True
|
||||
|
||||
|
||||
class RubyClassmember(RubyObject):
|
||||
"""
|
||||
Description of a class member (methods, attributes).
|
||||
"""
|
||||
|
||||
def needs_arglist(self):
|
||||
return self.objtype.endswith('method')
|
||||
|
||||
def get_signature_prefix(self, sig):
|
||||
if self.objtype == 'classmethod':
|
||||
return "classmethod %s." % self.class_name
|
||||
elif self.objtype == 'attr_reader':
|
||||
return "attribute [R] "
|
||||
elif self.objtype == 'attr_writer':
|
||||
return "attribute [W] "
|
||||
elif self.objtype == 'attr_accessor':
|
||||
return "attribute [R/W] "
|
||||
return ''
|
||||
|
||||
def get_index_text(self, modname, name_cls):
|
||||
name, cls = name_cls
|
||||
add_modules = self.env.config.add_module_names
|
||||
if self.objtype == 'classmethod':
|
||||
try:
|
||||
clsname, methname = ruby_rsplit(name)
|
||||
except ValueError:
|
||||
return '%s()' % name
|
||||
if modname:
|
||||
return _('%s() (%s.%s class method)') % (methname, modname,
|
||||
clsname)
|
||||
else:
|
||||
return _('%s() (%s class method)') % (methname, clsname)
|
||||
elif self.objtype.startswith('attr'):
|
||||
try:
|
||||
clsname, attrname = ruby_rsplit(name)
|
||||
except ValueError:
|
||||
return name
|
||||
if modname and add_modules:
|
||||
return _('%s (%s.%s attribute)') % (attrname, modname, clsname)
|
||||
else:
|
||||
return _('%s (%s attribute)') % (attrname, clsname)
|
||||
else:
|
||||
return ''
|
||||
|
||||
def before_content(self):
|
||||
RubyObject.before_content(self)
|
||||
lastname = self.names and self.names[-1][1]
|
||||
if lastname and not self.env.temp_data.get('rb:class'):
|
||||
self.env.temp_data['rb:class'] = lastname.strip('.')
|
||||
self.clsname_set = True
|
||||
|
||||
|
||||
class RubyModule(Directive):
|
||||
"""
|
||||
Directive to mark description of a new module.
|
||||
"""
|
||||
|
||||
has_content = False
|
||||
required_arguments = 1
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = False
|
||||
option_spec = {
|
||||
'platform': lambda x: x,
|
||||
'synopsis': lambda x: x,
|
||||
'noindex': directives.flag,
|
||||
'deprecated': directives.flag,
|
||||
}
|
||||
|
||||
def run(self):
|
||||
env = self.state.document.settings.env
|
||||
modname = self.arguments[0].strip()
|
||||
noindex = 'noindex' in self.options
|
||||
env.temp_data['rb:module'] = modname
|
||||
env.domaindata['rb']['modules'][modname] = \
|
||||
(env.docname, self.options.get('synopsis', ''),
|
||||
self.options.get('platform', ''), 'deprecated' in self.options)
|
||||
targetnode = nodes.target('', '', ids=['module-' + modname], ismod=True)
|
||||
self.state.document.note_explicit_target(targetnode)
|
||||
ret = [targetnode]
|
||||
# XXX this behavior of the module directive is a mess...
|
||||
if 'platform' in self.options:
|
||||
platform = self.options['platform']
|
||||
node = nodes.paragraph()
|
||||
node += nodes.emphasis('', _('Platforms: '))
|
||||
node += nodes.Text(platform, platform)
|
||||
ret.append(node)
|
||||
# the synopsis isn't printed; in fact, it is only used in the
|
||||
# modindex currently
|
||||
if not noindex:
|
||||
indextext = _('%s (module)') % modname
|
||||
inode = addnodes.index(entries=[('single', indextext,
|
||||
'module-' + modname, modname)])
|
||||
ret.append(inode)
|
||||
return ret
|
||||
|
||||
|
||||
class RubyCurrentModule(Directive):
|
||||
"""
|
||||
This directive is just to tell Sphinx that we're documenting
|
||||
stuff in module foo, but links to module foo won't lead here.
|
||||
"""
|
||||
|
||||
has_content = False
|
||||
required_arguments = 1
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = False
|
||||
option_spec = {}
|
||||
|
||||
def run(self):
|
||||
env = self.state.document.settings.env
|
||||
modname = self.arguments[0].strip()
|
||||
if modname == 'None':
|
||||
env.temp_data['rb:module'] = None
|
||||
else:
|
||||
env.temp_data['rb:module'] = modname
|
||||
return []
|
||||
|
||||
|
||||
class RubyXRefRole(XRefRole):
|
||||
def process_link(self, env, refnode, has_explicit_title, title, target):
|
||||
if not has_explicit_title:
|
||||
title = title.lstrip('.') # only has a meaning for the target
|
||||
title = title.lstrip('#')
|
||||
if title.startswith("::"):
|
||||
title = title[2:]
|
||||
target = target.lstrip('~') # only has a meaning for the title
|
||||
# if the first character is a tilde, don't display the module/class
|
||||
# parts of the contents
|
||||
if title[0:1] == '~':
|
||||
m = re.search(r"(?:\.)?(?:#)?(?:::)?(.*)\Z", title)
|
||||
if m:
|
||||
title = m.group(1)
|
||||
if not title.startswith("$"):
|
||||
refnode['rb:module'] = env.temp_data.get('rb:module')
|
||||
refnode['rb:class'] = env.temp_data.get('rb:class')
|
||||
# if the first character is a dot, search more specific namespaces first
|
||||
# else search builtins first
|
||||
if target[0:1] == '.':
|
||||
target = target[1:]
|
||||
refnode['refspecific'] = True
|
||||
return title, target
|
||||
|
||||
|
||||
class RubyModuleIndex(Index):
|
||||
"""
|
||||
Index subclass to provide the Ruby module index.
|
||||
"""
|
||||
|
||||
name = 'modindex'
|
||||
localname = l_('Ruby Module Index')
|
||||
shortname = l_('modules')
|
||||
|
||||
def generate(self, docnames=None):
|
||||
content = {}
|
||||
# list of prefixes to ignore
|
||||
ignores = self.domain.env.config['modindex_common_prefix']
|
||||
ignores = sorted(ignores, key=len, reverse=True)
|
||||
# list of all modules, sorted by module name
|
||||
modules = sorted(_iteritems(self.domain.data['modules']),
|
||||
key=lambda x: x[0].lower())
|
||||
# sort out collapsable modules
|
||||
prev_modname = ''
|
||||
num_toplevels = 0
|
||||
for modname, (docname, synopsis, platforms, deprecated) in modules:
|
||||
if docnames and docname not in docnames:
|
||||
continue
|
||||
|
||||
for ignore in ignores:
|
||||
if modname.startswith(ignore):
|
||||
modname = modname[len(ignore):]
|
||||
stripped = ignore
|
||||
break
|
||||
else:
|
||||
stripped = ''
|
||||
|
||||
# we stripped the whole module name?
|
||||
if not modname:
|
||||
modname, stripped = stripped, ''
|
||||
|
||||
entries = content.setdefault(modname[0].lower(), [])
|
||||
|
||||
package = modname.split('::')[0]
|
||||
if package != modname:
|
||||
# it's a submodule
|
||||
if prev_modname == package:
|
||||
# first submodule - make parent a group head
|
||||
entries[-1][1] = 1
|
||||
elif not prev_modname.startswith(package):
|
||||
# submodule without parent in list, add dummy entry
|
||||
entries.append([stripped + package, 1, '', '', '', '', ''])
|
||||
subtype = 2
|
||||
else:
|
||||
num_toplevels += 1
|
||||
subtype = 0
|
||||
|
||||
qualifier = deprecated and _('Deprecated') or ''
|
||||
entries.append([stripped + modname, subtype, docname,
|
||||
'module-' + stripped + modname, platforms,
|
||||
qualifier, synopsis])
|
||||
prev_modname = modname
|
||||
|
||||
# apply heuristics when to collapse modindex at page load:
|
||||
# only collapse if number of toplevel modules is larger than
|
||||
# number of submodules
|
||||
collapse = len(modules) - num_toplevels < num_toplevels
|
||||
|
||||
# sort by first letter
|
||||
content = sorted(_iteritems(content))
|
||||
|
||||
return content, collapse
|
||||
|
||||
|
||||
class RubyDomain(Domain):
|
||||
"""Ruby language domain."""
|
||||
name = 'rb'
|
||||
label = 'Ruby'
|
||||
object_types = {
|
||||
'function': ObjType(l_('function'), 'func', 'obj'),
|
||||
'global': ObjType(l_('global variable'), 'global', 'obj'),
|
||||
'method': ObjType(l_('method'), 'meth', 'obj'),
|
||||
'class': ObjType(l_('class'), 'class', 'obj'),
|
||||
'exception': ObjType(l_('exception'), 'exc', 'obj'),
|
||||
'classmethod': ObjType(l_('class method'), 'meth', 'obj'),
|
||||
'attr_reader': ObjType(l_('attribute'), 'attr', 'obj'),
|
||||
'attr_writer': ObjType(l_('attribute'), 'attr', 'obj'),
|
||||
'attr_accessor': ObjType(l_('attribute'), 'attr', 'obj'),
|
||||
'const': ObjType(l_('const'), 'const', 'obj'),
|
||||
'module': ObjType(l_('module'), 'mod', 'obj'),
|
||||
}
|
||||
|
||||
directives = {
|
||||
'function': RubyModulelevel,
|
||||
'global': RubyGloballevel,
|
||||
'method': RubyEverywhere,
|
||||
'const': RubyEverywhere,
|
||||
'class': RubyClasslike,
|
||||
'exception': RubyClasslike,
|
||||
'classmethod': RubyClassmember,
|
||||
'attr_reader': RubyClassmember,
|
||||
'attr_writer': RubyClassmember,
|
||||
'attr_accessor': RubyClassmember,
|
||||
'module': RubyModule,
|
||||
'currentmodule': RubyCurrentModule,
|
||||
}
|
||||
|
||||
roles = {
|
||||
'func': RubyXRefRole(fix_parens=False),
|
||||
'global':RubyXRefRole(),
|
||||
'class': RubyXRefRole(),
|
||||
'exc': RubyXRefRole(),
|
||||
'meth': RubyXRefRole(fix_parens=False),
|
||||
'attr': RubyXRefRole(),
|
||||
'const': RubyXRefRole(),
|
||||
'mod': RubyXRefRole(),
|
||||
'obj': RubyXRefRole(),
|
||||
}
|
||||
initial_data = {
|
||||
'objects': {}, # fullname -> docname, objtype
|
||||
'modules': {}, # modname -> docname, synopsis, platform, deprecated
|
||||
}
|
||||
indices = [
|
||||
RubyModuleIndex,
|
||||
]
|
||||
|
||||
def clear_doc(self, docname):
|
||||
for fullname, (fn, _) in list(self.data['objects'].items()):
|
||||
if fn == docname:
|
||||
del self.data['objects'][fullname]
|
||||
for modname, (fn, _, _, _) in list(self.data['modules'].items()):
|
||||
if fn == docname:
|
||||
del self.data['modules'][modname]
|
||||
|
||||
def find_obj(self, env, modname, classname, name, type, searchorder=0):
|
||||
"""
|
||||
Find a Ruby object for "name", perhaps using the given module and/or
|
||||
classname.
|
||||
"""
|
||||
# skip parens
|
||||
if name[-2:] == '()':
|
||||
name = name[:-2]
|
||||
|
||||
if not name:
|
||||
return None, None
|
||||
|
||||
objects = self.data['objects']
|
||||
|
||||
newname = None
|
||||
if searchorder == 1:
|
||||
if modname and classname and \
|
||||
modname + '::' + classname + '#' + name in objects:
|
||||
newname = modname + '::' + classname + '#' + name
|
||||
elif modname and classname and \
|
||||
modname + '::' + classname + '.' + name in objects:
|
||||
newname = modname + '::' + classname + '.' + name
|
||||
elif modname and modname + '::' + name in objects:
|
||||
newname = modname + '::' + name
|
||||
elif modname and modname + '#' + name in objects:
|
||||
newname = modname + '#' + name
|
||||
elif modname and modname + '.' + name in objects:
|
||||
newname = modname + '.' + name
|
||||
elif classname and classname + '.' + name in objects:
|
||||
newname = classname + '.' + name
|
||||
elif classname and classname + '#' + name in objects:
|
||||
newname = classname + '#' + name
|
||||
elif name in objects:
|
||||
newname = name
|
||||
else:
|
||||
if name in objects:
|
||||
newname = name
|
||||
elif classname and classname + '.' + name in objects:
|
||||
newname = classname + '.' + name
|
||||
elif classname and classname + '#' + name in objects:
|
||||
newname = classname + '#' + name
|
||||
elif modname and modname + '::' + name in objects:
|
||||
newname = modname + '::' + name
|
||||
elif modname and modname + '#' + name in objects:
|
||||
newname = modname + '#' + name
|
||||
elif modname and modname + '.' + name in objects:
|
||||
newname = modname + '.' + name
|
||||
elif modname and classname and \
|
||||
modname + '::' + classname + '#' + name in objects:
|
||||
newname = modname + '::' + classname + '#' + name
|
||||
elif modname and classname and \
|
||||
modname + '::' + classname + '.' + name in objects:
|
||||
newname = modname + '::' + classname + '.' + name
|
||||
# special case: object methods
|
||||
elif type in ('func', 'meth') and '.' not in name and \
|
||||
'object.' + name in objects:
|
||||
newname = 'object.' + name
|
||||
if newname is None:
|
||||
return None, None
|
||||
return newname, objects[newname]
|
||||
|
||||
def resolve_xref(self, env, fromdocname, builder,
|
||||
typ, target, node, contnode):
|
||||
if (typ == 'mod' or
|
||||
typ == 'obj' and target in self.data['modules']):
|
||||
docname, synopsis, platform, deprecated = \
|
||||
self.data['modules'].get(target, ('','','', ''))
|
||||
if not docname:
|
||||
return None
|
||||
else:
|
||||
title = '%s%s%s' % ((platform and '(%s) ' % platform),
|
||||
synopsis,
|
||||
(deprecated and ' (deprecated)' or ''))
|
||||
return make_refnode(builder, fromdocname, docname,
|
||||
'module-' + target, contnode, title)
|
||||
else:
|
||||
modname = node.get('rb:module')
|
||||
clsname = node.get('rb:class')
|
||||
searchorder = node.hasattr('refspecific') and 1 or 0
|
||||
name, obj = self.find_obj(env, modname, clsname,
|
||||
target, typ, searchorder)
|
||||
if not obj:
|
||||
return None
|
||||
else:
|
||||
return make_refnode(builder, fromdocname, obj[0], name,
|
||||
contnode, name)
|
||||
|
||||
def get_objects(self):
|
||||
for modname, info in _iteritems(self.data['modules']):
|
||||
yield (modname, modname, 'module', info[0], 'module-' + modname, 0)
|
||||
for refname, (docname, type) in _iteritems(self.data['objects']):
|
||||
yield (refname, refname, type, docname, refname, 1)
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.add_domain(RubyDomain)
|
||||
@@ -8,7 +8,7 @@ _nghttpx()
|
||||
_get_comp_words_by_ref cur prev
|
||||
case $cur in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W '--worker-read-rate --frontend-no-tls --frontend-http2-dump-response-header --backend-http1-connections-per-frontend --tls-ticket-key-file --verify-client-cacert --include --backend-request-buffer --backend-http2-connection-window-bits --conf --worker-write-burst --npn-list --fetch-ocsp-response-file --stream-read-timeout --tls-ticket-key-memcached --accesslog-syslog --frontend-http2-read-timeout --listener-disable-timeout --frontend-http2-connection-window-bits --ciphers --strip-incoming-x-forwarded-for --private-key-passwd-file --backend-keep-alive-timeout --backend-http-proxy-uri --backend-http1-connections-per-host --rlimit-nofile --no-via --ocsp-update-interval --backend-write-timeout --client --tls-ticket-key-memcached-max-retry --http2-no-cookie-crumbling --worker-read-burst --client-proxy --http2-bridge --accesslog-format --errorlog-syslog --errorlog-file --http2-max-concurrent-streams --frontend-write-timeout --tls-ticket-key-cipher --read-burst --backend-ipv4 --backend-ipv6 --backend --insecure --log-level --host-rewrite --tls-proto-list --backend-http2-connections-per-worker --tls-ticket-key-memcached-interval --dh-param-file --worker-frontend-connections --header-field-buffer --no-server-push --no-location-rewrite --tls-session-cache-memcached --no-ocsp --backend-response-buffer --workers --frontend-http2-window-bits --worker-write-rate --add-request-header --backend-tls-sni-field --subcert --help --frontend-frame-debug --pid-file --frontend-http2-dump-request-header --daemon --write-rate --altsvc --user --add-x-forwarded-for --syslog-facility --frontend-read-timeout --tls-ticket-key-memcached-max-fail --backlog --write-burst --backend-http2-window-bits --padding --stream-write-timeout --cacert --version --verify-client --backend-read-timeout --frontend --accesslog-file --http2-proxy --max-header-fields --backend-no-tls --client-private-key-file --client-cert-file --add-response-header --read-rate ' -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W '--worker-read-rate --frontend-no-tls --frontend-http2-dump-response-header --backend-http1-connections-per-frontend --tls-ticket-key-file --verify-client-cacert --include --backend-request-buffer --backend-http2-connection-window-bits --conf --worker-write-burst --npn-list --fetch-ocsp-response-file --stream-read-timeout --tls-ticket-key-memcached --accesslog-syslog --frontend-http2-read-timeout --listener-disable-timeout --frontend-http2-connection-window-bits --ciphers --strip-incoming-x-forwarded-for --private-key-passwd-file --backend-keep-alive-timeout --backend-http-proxy-uri --backend-http1-connections-per-host --rlimit-nofile --no-via --ocsp-update-interval --backend-write-timeout --client --tls-ticket-key-memcached-max-retry --http2-no-cookie-crumbling --worker-read-burst --client-proxy --http2-bridge --accesslog-format --errorlog-syslog --errorlog-file --http2-max-concurrent-streams --frontend-write-timeout --tls-ticket-key-cipher --response-phase-file --read-burst --backend-ipv4 --backend-ipv6 --backend --insecure --log-level --host-rewrite --tls-proto-list --backend-http2-connections-per-worker --tls-ticket-key-memcached-interval --dh-param-file --worker-frontend-connections --header-field-buffer --no-server-push --no-location-rewrite --tls-session-cache-memcached --no-ocsp --backend-response-buffer --workers --frontend-http2-window-bits --worker-write-rate --add-request-header --backend-tls-sni-field --subcert --help --frontend-frame-debug --pid-file --frontend-http2-dump-request-header --daemon --write-rate --altsvc --user --add-x-forwarded-for --syslog-facility --frontend-read-timeout --tls-ticket-key-memcached-max-fail --backlog --write-burst --backend-http2-window-bits --padding --stream-write-timeout --cacert --version --verify-client --request-phase-file --backend-read-timeout --frontend --accesslog-file --http2-proxy --max-header-fields --backend-no-tls --client-private-key-file --client-cert-file --accept-proxy-protocol --add-response-header --read-rate ' -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
_filedir
|
||||
|
||||
@@ -41,6 +41,8 @@ import sys, os
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
sys.path.append(os.path.abspath('_exts'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
@@ -48,7 +50,7 @@ import sys, os
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = []
|
||||
extensions = ['sphinxcontrib.rubydomain']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['@top_srcdir@/_templates']
|
||||
|
||||
20
doc/h2load.1
20
doc/h2load.1
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "H2LOAD" "1" "August 30, 2015" "1.3.0" "nghttp2"
|
||||
.TH "H2LOAD" "1" "September 12, 2015" "1.3.1" "nghttp2"
|
||||
.SH NAME
|
||||
h2load \- HTTP/2 benchmarking tool
|
||||
.
|
||||
@@ -185,15 +185,15 @@ indefinitely, waiting for a response.
|
||||
Path of a file containing one or more lines separated by
|
||||
EOLs. Each script line is composed of two tab\-separated
|
||||
fields. The first field represents the time offset from
|
||||
the start of execution, expressed as milliseconds with
|
||||
microsecond resolution. The second field represents the
|
||||
URI. This option will disable URIs getting from
|
||||
command\-line. If \(aq\-\(aq is given as <PATH>, script lines
|
||||
will be read from stdin. Script lines are used in order
|
||||
for each client. If \fI\%\-n\fP is given, it must be less than
|
||||
or equal to the number of script lines, larger values are
|
||||
clamped to the number of script lines. If \fI\%\-n\fP is
|
||||
not given, the number of requests will default to the
|
||||
the start of execution, expressed as a positive value of
|
||||
milliseconds with microsecond resolution. The second
|
||||
field represents the URI. This option will disable URIs
|
||||
getting from command\-line. If \(aq\-\(aq is given as <PATH>,
|
||||
script lines will be read from stdin. Script lines are
|
||||
used in order for each client. If \fI\%\-n\fP is given, it must be
|
||||
less than or equal to the number of script lines, larger
|
||||
values are clamped to the number of script lines. If \fI\%\-n\fP
|
||||
is not given, the number of requests will default to the
|
||||
number of script lines. The scheme, host and port defined
|
||||
in the first URI are used solely. Values contained in
|
||||
other URIs, if present, are ignored. Definition of a
|
||||
|
||||
@@ -150,15 +150,15 @@ OPTIONS
|
||||
Path of a file containing one or more lines separated by
|
||||
EOLs. Each script line is composed of two tab-separated
|
||||
fields. The first field represents the time offset from
|
||||
the start of execution, expressed as milliseconds with
|
||||
microsecond resolution. The second field represents the
|
||||
URI. This option will disable URIs getting from
|
||||
command-line. If '-' is given as <PATH>, script lines
|
||||
will be read from stdin. Script lines are used in order
|
||||
for each client. If :option:`-n` is given, it must be less than
|
||||
or equal to the number of script lines, larger values are
|
||||
clamped to the number of script lines. If :option:`-n` is
|
||||
not given, the number of requests will default to the
|
||||
the start of execution, expressed as a positive value of
|
||||
milliseconds with microsecond resolution. The second
|
||||
field represents the URI. This option will disable URIs
|
||||
getting from command-line. If '-' is given as <PATH>,
|
||||
script lines will be read from stdin. Script lines are
|
||||
used in order for each client. If :option:`-n` is given, it must be
|
||||
less than or equal to the number of script lines, larger
|
||||
values are clamped to the number of script lines. If :option:`-n`
|
||||
is not given, the number of requests will default to the
|
||||
number of script lines. The scheme, host and port defined
|
||||
in the first URI are used solely. Values contained in
|
||||
other URIs, if present, are ignored. Definition of a
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "NGHTTP" "1" "August 30, 2015" "1.3.0" "nghttp2"
|
||||
.TH "NGHTTP" "1" "September 12, 2015" "1.3.1" "nghttp2"
|
||||
.SH NAME
|
||||
nghttp \- HTTP/2 experimental client
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "NGHTTPD" "1" "August 30, 2015" "1.3.0" "nghttp2"
|
||||
.TH "NGHTTPD" "1" "September 12, 2015" "1.3.1" "nghttp2"
|
||||
.SH NAME
|
||||
nghttpd \- HTTP/2 experimental server
|
||||
.
|
||||
|
||||
303
doc/nghttpx.1
303
doc/nghttpx.1
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "NGHTTPX" "1" "August 30, 2015" "1.3.0" "nghttp2"
|
||||
.TH "NGHTTPX" "1" "September 12, 2015" "1.3.1" "nghttp2"
|
||||
.SH NAME
|
||||
nghttpx \- HTTP/2 experimental proxy
|
||||
.
|
||||
@@ -158,6 +158,11 @@ timeouts when connecting and making CONNECT request can
|
||||
be specified by \fI\%\-\-backend\-read\-timeout\fP and
|
||||
\fI\%\-\-backend\-write\-timeout\fP options.
|
||||
.UNINDENT
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B \-\-accept\-proxy\-protocol
|
||||
Accept PROXY protocol version 1 on frontend connection.
|
||||
.UNINDENT
|
||||
.SS Performance
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
@@ -905,6 +910,21 @@ Set path to save PID of this program.
|
||||
Run this program as <USER>. This option is intended to
|
||||
be used to drop root privileges.
|
||||
.UNINDENT
|
||||
.SS Scripting
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B \-\-request\-phase\-file=<PATH>
|
||||
Set mruby script file which will be executed when
|
||||
request header fields are completely received from
|
||||
frontend. This hook is called request phase hook.
|
||||
.UNINDENT
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B \-\-response\-phase\-file=<PATH>
|
||||
Set mruby script file which will be executed when
|
||||
response header fields are completely received from
|
||||
backend. This hook is called response phase hook.
|
||||
.UNINDENT
|
||||
.SS Misc
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
@@ -1003,19 +1023,14 @@ Link: </css/theme.css>; rel=preload
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
Currently, the following restrictions are applied for server push:
|
||||
Currently, the following restriction is applied for server push:
|
||||
.INDENT 0.0
|
||||
.IP 1. 3
|
||||
URI\-reference must not contain authority. If it exists, it is not
|
||||
pushed. \fB/fonts/font.woff\fP and \fBcss/theme.css\fP are eligible to
|
||||
be pushed. \fBhttps://example.org/fonts/font.woff\fP and
|
||||
\fB//example.org/css/theme.css\fP are not.
|
||||
.IP 2. 3
|
||||
The associated stream must have method "GET" or "POST". The
|
||||
associated stream\(aqs status code must be 200.
|
||||
.UNINDENT
|
||||
.sp
|
||||
These limitations may be loosened in the future release.
|
||||
This limitation may be loosened in the future release.
|
||||
.SH UNIX DOMAIN SOCKET
|
||||
.sp
|
||||
nghttpx supports UNIX domain socket with a filename for both frontend
|
||||
@@ -1101,6 +1116,278 @@ If \fI\%\-\-tls\-ticket\-key\-file\fP is given, encryption key is read
|
||||
from the given file. In this case, nghttpx does not rotate key
|
||||
automatically. To rotate key, one has to restart nghttpx (see
|
||||
SIGNALS).
|
||||
.SH MRUBY SCRIPTING
|
||||
.sp
|
||||
\fBWARNING:\fP
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
The current mruby extension API is experimental and not frozen. The
|
||||
API is subject to change in the future release.
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
nghttpx allows users to extend its capability using mruby scripts.
|
||||
nghttpx has 2 hook points to execute mruby script: request phase and
|
||||
response phase. The request phase hook is invoked after all request
|
||||
header fields are received from client. The response phase hook is
|
||||
invoked after all response header fields are received from backend
|
||||
server. These hooks allows users to modify header fields, or common
|
||||
HTTP variables, like authority or request path, and even return custom
|
||||
response without forwarding request to backend servers.
|
||||
.sp
|
||||
To set request phase hook, use \fI\%\-\-request\-phase\-file\fP option.
|
||||
To set response phase hook, use \fI\%\-\-response\-phase\-file\fP
|
||||
option.
|
||||
.sp
|
||||
For request and response phase hook, user calls \fI\%Nghttpx.run\fP
|
||||
with block. The \fI\%Nghttpx::Env\fP is passed to the block.
|
||||
User can can access \fI\%Nghttpx::Request\fP and
|
||||
\fI\%Nghttpx::Response\fP objects via \fI\%Nghttpx::Env#req\fP
|
||||
and \fI\%Nghttpx::Env#resp\fP respectively.
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B classmethod .Nghttpx.run(&block)
|
||||
Run request or response phase hook with given \fIblock\fP\&.
|
||||
\fI\%Nghttpx::Env\fP object is passed to the given block.
|
||||
.UNINDENT
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B Nghttpx::REQUEST_PHASE
|
||||
Constant to represent request phase.
|
||||
.UNINDENT
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B Nghttpx::RESPONSE_PHASE
|
||||
Constant to represent response phase.
|
||||
.UNINDENT
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B class Nghttpx::Env
|
||||
Object to represent current request specific context.
|
||||
.INDENT 7.0
|
||||
.TP
|
||||
.B attribute [R] req
|
||||
Return \fI\%Request\fP object.
|
||||
.UNINDENT
|
||||
.INDENT 7.0
|
||||
.TP
|
||||
.B attribute [R] resp
|
||||
Return \fI\%Response\fP object.
|
||||
.UNINDENT
|
||||
.INDENT 7.0
|
||||
.TP
|
||||
.B attribute [R] ctx
|
||||
Return Ruby hash object. It persists until request finishes.
|
||||
So values set in request phase hoo can be retrieved in
|
||||
response phase hook.
|
||||
.UNINDENT
|
||||
.INDENT 7.0
|
||||
.TP
|
||||
.B attribute [R] phase
|
||||
Return the current phase.
|
||||
.UNINDENT
|
||||
.INDENT 7.0
|
||||
.TP
|
||||
.B attribute [R] remote_addr
|
||||
Return IP address of a remote client.
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B class Nghttpx::Request
|
||||
Object to represent request from client. The modification to
|
||||
Request object is allowed only in request phase hook.
|
||||
.INDENT 7.0
|
||||
.TP
|
||||
.B attribute [R] http_version_major
|
||||
Return HTTP major version.
|
||||
.UNINDENT
|
||||
.INDENT 7.0
|
||||
.TP
|
||||
.B attribute [R] http_version_minor
|
||||
Return HTTP minor version.
|
||||
.UNINDENT
|
||||
.INDENT 7.0
|
||||
.TP
|
||||
.B attribute [R/W] method
|
||||
HTTP method. On assignment, copy of given value is assigned.
|
||||
We don\(aqt accept arbitrary method name. We will document them
|
||||
later, but well known methods, like GET, PUT and POST, are all
|
||||
supported.
|
||||
.UNINDENT
|
||||
.INDENT 7.0
|
||||
.TP
|
||||
.B attribute [R/W] authority
|
||||
Authority (i.e., example.org), including optional port
|
||||
component . On assignment, copy of given value is assigned.
|
||||
.UNINDENT
|
||||
.INDENT 7.0
|
||||
.TP
|
||||
.B attribute [R/W] scheme
|
||||
Scheme (i.e., http, https). On assignment, copy of given
|
||||
value is assigned.
|
||||
.UNINDENT
|
||||
.INDENT 7.0
|
||||
.TP
|
||||
.B attribute [R/W] path
|
||||
Request path, including query component (i.e., /index.html).
|
||||
On assignment, copy of given value is assigned. The path does
|
||||
not include authority component of URI.
|
||||
.UNINDENT
|
||||
.INDENT 7.0
|
||||
.TP
|
||||
.B attribute [R] headers
|
||||
Return Ruby hash containing copy of request header fields.
|
||||
Changing values in returned hash does not change request
|
||||
header fields actually used in request processing. Use
|
||||
\fI\%Nghttpx::Request#add_header\fP or
|
||||
\fI\%Nghttpx::Request#set_header\fP to change request
|
||||
header fields.
|
||||
.UNINDENT
|
||||
.INDENT 7.0
|
||||
.TP
|
||||
.B add_header(key, value)
|
||||
Add header entry associated with key. The value can be single
|
||||
string or array of string. It does not replace any existing
|
||||
values associated with key.
|
||||
.UNINDENT
|
||||
.INDENT 7.0
|
||||
.TP
|
||||
.B set_header(key, value)
|
||||
Set header entry associated with key. The value can be single
|
||||
string or array of string. It replaces any existing values
|
||||
associated with key.
|
||||
.UNINDENT
|
||||
.INDENT 7.0
|
||||
.TP
|
||||
.B clear_headers()
|
||||
Clear all existing request header fields.
|
||||
.UNINDENT
|
||||
.INDENT 7.0
|
||||
.TP
|
||||
.B push uri
|
||||
Initiate to push resource identified by \fIuri\fP\&. Only HTTP/2
|
||||
protocol supports this feature. For the other protocols, this
|
||||
method is noop. \fIuri\fP can be absolute URI, absolute path or
|
||||
relative path to the current request. For absolute or
|
||||
relative path, scheme and authority are inherited from the
|
||||
current request. Currently, method is always GET. nghttpx
|
||||
will issue request to backend servers to fulfill this request.
|
||||
The request and response phase hooks will be called for pushed
|
||||
resource as well.
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B class Nghttpx::Response
|
||||
Object to represent response from backend server.
|
||||
.INDENT 7.0
|
||||
.TP
|
||||
.B attribute [R] http_version_major
|
||||
Return HTTP major version.
|
||||
.UNINDENT
|
||||
.INDENT 7.0
|
||||
.TP
|
||||
.B attribute [R] http_version_minor
|
||||
Return HTTP minor version.
|
||||
.UNINDENT
|
||||
.INDENT 7.0
|
||||
.TP
|
||||
.B attribute [R/W] status
|
||||
HTTP status code. It must be in the range [200, 999],
|
||||
inclusive. The non\-final status code is not supported in
|
||||
mruby scripting at the moment.
|
||||
.UNINDENT
|
||||
.INDENT 7.0
|
||||
.TP
|
||||
.B attribute [R] headers
|
||||
Return Ruby hash containing copy of response header fields.
|
||||
Changing values in returned hash does not change response
|
||||
header fields actually used in response processing. Use
|
||||
\fI\%Nghttpx::Response#add_header\fP or
|
||||
\fI\%Nghttpx::Response#set_header\fP to change response
|
||||
header fields.
|
||||
.UNINDENT
|
||||
.INDENT 7.0
|
||||
.TP
|
||||
.B add_header(key, value)
|
||||
Add header entry associated with key. The value can be single
|
||||
string or array of string. It does not replace any existing
|
||||
values associated with key.
|
||||
.UNINDENT
|
||||
.INDENT 7.0
|
||||
.TP
|
||||
.B set_header(key, value)
|
||||
Set header entry associated with key. The value can be single
|
||||
string or array of string. It replaces any existing values
|
||||
associated with key.
|
||||
.UNINDENT
|
||||
.INDENT 7.0
|
||||
.TP
|
||||
.B clear_headers()
|
||||
Clear all existing response header fields.
|
||||
.UNINDENT
|
||||
.INDENT 7.0
|
||||
.TP
|
||||
.B return(body)
|
||||
Return custom response \fIbody\fP to a client. When this method
|
||||
is called in request phase hook, the request is not forwarded
|
||||
to the backend, and response phase hook for this request will
|
||||
not be invoked. When this method is called in resonse phase
|
||||
hook, response from backend server is canceled and discarded.
|
||||
The status code and response header fields should be set
|
||||
before using this method. To set status code, use :rb:meth To
|
||||
set response header fields, use
|
||||
\fI\%Nghttpx::Response#status\fP\&. If status code is not
|
||||
set, 200 is used. \fI\%Nghttpx::Response#add_header\fP and
|
||||
\fI\%Nghttpx::Response#set_header\fP\&. When this method is
|
||||
invoked in response phase hook, the response headers are
|
||||
filled with the ones received from backend server. To send
|
||||
completely custom header fields, first call
|
||||
\fI\%Nghttpx::Response#clear_headers\fP to erase all
|
||||
existing header fields, and then add required header fields.
|
||||
It is an error to call this method twice for a given request.
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SS MRUBY EXAMPLES
|
||||
.sp
|
||||
Modify requet path:
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
Nghttpx.run do |env|
|
||||
env.req.path = "/apps#{env.req.path}"
|
||||
end
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
Note that the file containing the above script must be set with
|
||||
\fI\%\-\-request\-phase\-file\fP option since we modify request path.
|
||||
.sp
|
||||
Restrict permission of viewing a content to a specific client
|
||||
addresses:
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
Nghttpx.run do |env|
|
||||
allowed_clients = ["127.0.0.1", "::1"]
|
||||
|
||||
if env.req.path.start_with?("/log/") &&
|
||||
!allowed_clients.include?(env.remote_addr) then
|
||||
env.resp.status = 404
|
||||
env.resp.return "permission denied"
|
||||
end
|
||||
end
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.SH SEE ALSO
|
||||
.sp
|
||||
\fInghttp(1)\fP, \fInghttpd(1)\fP, \fIh2load(1)\fP
|
||||
|
||||
@@ -137,6 +137,10 @@ Connections
|
||||
be specified by :option:`--backend-read-timeout` and
|
||||
:option:`--backend-write-timeout` options.
|
||||
|
||||
.. option:: --accept-proxy-protocol
|
||||
|
||||
Accept PROXY protocol version 1 on frontend connection.
|
||||
|
||||
|
||||
Performance
|
||||
~~~~~~~~~~~
|
||||
@@ -811,6 +815,22 @@ Process
|
||||
be used to drop root privileges.
|
||||
|
||||
|
||||
Scripting
|
||||
~~~~~~~~~
|
||||
|
||||
.. option:: --request-phase-file=<PATH>
|
||||
|
||||
Set mruby script file which will be executed when
|
||||
request header fields are completely received from
|
||||
frontend. This hook is called request phase hook.
|
||||
|
||||
.. option:: --response-phase-file=<PATH>
|
||||
|
||||
Set mruby script file which will be executed when
|
||||
response header fields are completely received from
|
||||
backend. This hook is called response phase hook.
|
||||
|
||||
|
||||
Misc
|
||||
~~~~
|
||||
|
||||
@@ -906,17 +926,12 @@ header field to initiate server push:
|
||||
Link: </fonts/font.woff>; rel=preload
|
||||
Link: </css/theme.css>; rel=preload
|
||||
|
||||
Currently, the following restrictions are applied for server push:
|
||||
Currently, the following restriction is applied for server push:
|
||||
|
||||
1. URI-reference must not contain authority. If it exists, it is not
|
||||
pushed. ``/fonts/font.woff`` and ``css/theme.css`` are eligible to
|
||||
be pushed. ``https://example.org/fonts/font.woff`` and
|
||||
``//example.org/css/theme.css`` are not.
|
||||
|
||||
2. The associated stream must have method "GET" or "POST". The
|
||||
1. The associated stream must have method "GET" or "POST". The
|
||||
associated stream's status code must be 200.
|
||||
|
||||
These limitations may be loosened in the future release.
|
||||
This limitation may be loosened in the future release.
|
||||
|
||||
UNIX DOMAIN SOCKET
|
||||
------------------
|
||||
@@ -1005,6 +1020,239 @@ from the given file. In this case, nghttpx does not rotate key
|
||||
automatically. To rotate key, one has to restart nghttpx (see
|
||||
SIGNALS).
|
||||
|
||||
MRUBY SCRIPTING
|
||||
---------------
|
||||
|
||||
.. warning::
|
||||
|
||||
The current mruby extension API is experimental and not frozen. The
|
||||
API is subject to change in the future release.
|
||||
|
||||
nghttpx allows users to extend its capability using mruby scripts.
|
||||
nghttpx has 2 hook points to execute mruby script: request phase and
|
||||
response phase. The request phase hook is invoked after all request
|
||||
header fields are received from client. The response phase hook is
|
||||
invoked after all response header fields are received from backend
|
||||
server. These hooks allows users to modify header fields, or common
|
||||
HTTP variables, like authority or request path, and even return custom
|
||||
response without forwarding request to backend servers.
|
||||
|
||||
To set request phase hook, use :option:`--request-phase-file` option.
|
||||
To set response phase hook, use :option:`--response-phase-file`
|
||||
option.
|
||||
|
||||
For request and response phase hook, user calls :rb:meth:`Nghttpx.run`
|
||||
with block. The :rb:class:`Nghttpx::Env` is passed to the block.
|
||||
User can can access :rb:class:`Nghttpx::Request` and
|
||||
:rb:class:`Nghttpx::Response` objects via :rb:attr:`Nghttpx::Env#req`
|
||||
and :rb:attr:`Nghttpx::Env#resp` respectively.
|
||||
|
||||
.. rb:module:: Nghttpx
|
||||
|
||||
.. rb:classmethod:: run(&block)
|
||||
|
||||
Run request or response phase hook with given *block*.
|
||||
:rb:class:`Nghttpx::Env` object is passed to the given block.
|
||||
|
||||
.. rb:const:: REQUEST_PHASE
|
||||
|
||||
Constant to represent request phase.
|
||||
|
||||
.. rb:const:: RESPONSE_PHASE
|
||||
|
||||
Constant to represent response phase.
|
||||
|
||||
.. rb:class:: Env
|
||||
|
||||
Object to represent current request specific context.
|
||||
|
||||
.. rb:attr_reader:: req
|
||||
|
||||
Return :rb:class:`Request` object.
|
||||
|
||||
.. rb:attr_reader:: resp
|
||||
|
||||
Return :rb:class:`Response` object.
|
||||
|
||||
.. rb:attr_reader:: ctx
|
||||
|
||||
Return Ruby hash object. It persists until request finishes.
|
||||
So values set in request phase hoo can be retrieved in
|
||||
response phase hook.
|
||||
|
||||
.. rb:attr_reader:: phase
|
||||
|
||||
Return the current phase.
|
||||
|
||||
.. rb:attr_reader:: remote_addr
|
||||
|
||||
Return IP address of a remote client.
|
||||
|
||||
.. rb:class:: Request
|
||||
|
||||
Object to represent request from client. The modification to
|
||||
Request object is allowed only in request phase hook.
|
||||
|
||||
.. rb:attr_reader:: http_version_major
|
||||
|
||||
Return HTTP major version.
|
||||
|
||||
.. rb:attr_reader:: http_version_minor
|
||||
|
||||
Return HTTP minor version.
|
||||
|
||||
.. rb:attr_accessor:: method
|
||||
|
||||
HTTP method. On assignment, copy of given value is assigned.
|
||||
We don't accept arbitrary method name. We will document them
|
||||
later, but well known methods, like GET, PUT and POST, are all
|
||||
supported.
|
||||
|
||||
.. rb:attr_accessor:: authority
|
||||
|
||||
Authority (i.e., example.org), including optional port
|
||||
component . On assignment, copy of given value is assigned.
|
||||
|
||||
.. rb:attr_accessor:: scheme
|
||||
|
||||
Scheme (i.e., http, https). On assignment, copy of given
|
||||
value is assigned.
|
||||
|
||||
.. rb:attr_accessor:: path
|
||||
|
||||
Request path, including query component (i.e., /index.html).
|
||||
On assignment, copy of given value is assigned. The path does
|
||||
not include authority component of URI.
|
||||
|
||||
.. rb:attr_reader:: headers
|
||||
|
||||
Return Ruby hash containing copy of request header fields.
|
||||
Changing values in returned hash does not change request
|
||||
header fields actually used in request processing. Use
|
||||
:rb:meth:`Nghttpx::Request#add_header` or
|
||||
:rb:meth:`Nghttpx::Request#set_header` to change request
|
||||
header fields.
|
||||
|
||||
.. rb:method:: add_header(key, value)
|
||||
|
||||
Add header entry associated with key. The value can be single
|
||||
string or array of string. It does not replace any existing
|
||||
values associated with key.
|
||||
|
||||
.. rb:method:: set_header(key, value)
|
||||
|
||||
Set header entry associated with key. The value can be single
|
||||
string or array of string. It replaces any existing values
|
||||
associated with key.
|
||||
|
||||
.. rb:method:: clear_headers
|
||||
|
||||
Clear all existing request header fields.
|
||||
|
||||
.. rb:method:: push uri
|
||||
|
||||
Initiate to push resource identified by *uri*. Only HTTP/2
|
||||
protocol supports this feature. For the other protocols, this
|
||||
method is noop. *uri* can be absolute URI, absolute path or
|
||||
relative path to the current request. For absolute or
|
||||
relative path, scheme and authority are inherited from the
|
||||
current request. Currently, method is always GET. nghttpx
|
||||
will issue request to backend servers to fulfill this request.
|
||||
The request and response phase hooks will be called for pushed
|
||||
resource as well.
|
||||
|
||||
.. rb:class:: Response
|
||||
|
||||
Object to represent response from backend server.
|
||||
|
||||
.. rb:attr_reader:: http_version_major
|
||||
|
||||
Return HTTP major version.
|
||||
|
||||
.. rb:attr_reader:: http_version_minor
|
||||
|
||||
Return HTTP minor version.
|
||||
|
||||
.. rb:attr_accessor:: status
|
||||
|
||||
HTTP status code. It must be in the range [200, 999],
|
||||
inclusive. The non-final status code is not supported in
|
||||
mruby scripting at the moment.
|
||||
|
||||
.. rb:attr_reader:: headers
|
||||
|
||||
Return Ruby hash containing copy of response header fields.
|
||||
Changing values in returned hash does not change response
|
||||
header fields actually used in response processing. Use
|
||||
:rb:meth:`Nghttpx::Response#add_header` or
|
||||
:rb:meth:`Nghttpx::Response#set_header` to change response
|
||||
header fields.
|
||||
|
||||
.. rb:method:: add_header(key, value)
|
||||
|
||||
Add header entry associated with key. The value can be single
|
||||
string or array of string. It does not replace any existing
|
||||
values associated with key.
|
||||
|
||||
.. rb:method:: set_header(key, value)
|
||||
|
||||
Set header entry associated with key. The value can be single
|
||||
string or array of string. It replaces any existing values
|
||||
associated with key.
|
||||
|
||||
.. rb:method:: clear_headers
|
||||
|
||||
Clear all existing response header fields.
|
||||
|
||||
.. rb:method:: return(body)
|
||||
|
||||
Return custom response *body* to a client. When this method
|
||||
is called in request phase hook, the request is not forwarded
|
||||
to the backend, and response phase hook for this request will
|
||||
not be invoked. When this method is called in resonse phase
|
||||
hook, response from backend server is canceled and discarded.
|
||||
The status code and response header fields should be set
|
||||
before using this method. To set status code, use :rb:meth To
|
||||
set response header fields, use
|
||||
:rb:attr:`Nghttpx::Response#status`. If status code is not
|
||||
set, 200 is used. :rb:meth:`Nghttpx::Response#add_header` and
|
||||
:rb:meth:`Nghttpx::Response#set_header`. When this method is
|
||||
invoked in response phase hook, the response headers are
|
||||
filled with the ones received from backend server. To send
|
||||
completely custom header fields, first call
|
||||
:rb:meth:`Nghttpx::Response#clear_headers` to erase all
|
||||
existing header fields, and then add required header fields.
|
||||
It is an error to call this method twice for a given request.
|
||||
|
||||
MRUBY EXAMPLES
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Modify requet path:
|
||||
|
||||
.. code-block:: ruby
|
||||
|
||||
Nghttpx.run do |env|
|
||||
env.req.path = "/apps#{env.req.path}"
|
||||
end
|
||||
|
||||
Note that the file containing the above script must be set with
|
||||
:option:`--request-phase-file` option since we modify request path.
|
||||
|
||||
Restrict permission of viewing a content to a specific client
|
||||
addresses:
|
||||
|
||||
.. code-block:: ruby
|
||||
|
||||
Nghttpx.run do |env|
|
||||
allowed_clients = ["127.0.0.1", "::1"]
|
||||
|
||||
if env.req.path.start_with?("/log/") &&
|
||||
!allowed_clients.include?(env.remote_addr) then
|
||||
env.resp.status = 404
|
||||
env.resp.return "permission denied"
|
||||
end
|
||||
end
|
||||
|
||||
SEE ALSO
|
||||
--------
|
||||
|
||||
|
||||
244
doc/nghttpx.h2r
244
doc/nghttpx.h2r
@@ -59,17 +59,12 @@ header field to initiate server push:
|
||||
Link: </fonts/font.woff>; rel=preload
|
||||
Link: </css/theme.css>; rel=preload
|
||||
|
||||
Currently, the following restrictions are applied for server push:
|
||||
Currently, the following restriction is applied for server push:
|
||||
|
||||
1. URI-reference must not contain authority. If it exists, it is not
|
||||
pushed. ``/fonts/font.woff`` and ``css/theme.css`` are eligible to
|
||||
be pushed. ``https://example.org/fonts/font.woff`` and
|
||||
``//example.org/css/theme.css`` are not.
|
||||
|
||||
2. The associated stream must have method "GET" or "POST". The
|
||||
1. The associated stream must have method "GET" or "POST". The
|
||||
associated stream's status code must be 200.
|
||||
|
||||
These limitations may be loosened in the future release.
|
||||
This limitation may be loosened in the future release.
|
||||
|
||||
UNIX DOMAIN SOCKET
|
||||
------------------
|
||||
@@ -158,6 +153,239 @@ from the given file. In this case, nghttpx does not rotate key
|
||||
automatically. To rotate key, one has to restart nghttpx (see
|
||||
SIGNALS).
|
||||
|
||||
MRUBY SCRIPTING
|
||||
---------------
|
||||
|
||||
.. warning::
|
||||
|
||||
The current mruby extension API is experimental and not frozen. The
|
||||
API is subject to change in the future release.
|
||||
|
||||
nghttpx allows users to extend its capability using mruby scripts.
|
||||
nghttpx has 2 hook points to execute mruby script: request phase and
|
||||
response phase. The request phase hook is invoked after all request
|
||||
header fields are received from client. The response phase hook is
|
||||
invoked after all response header fields are received from backend
|
||||
server. These hooks allows users to modify header fields, or common
|
||||
HTTP variables, like authority or request path, and even return custom
|
||||
response without forwarding request to backend servers.
|
||||
|
||||
To set request phase hook, use :option:`--request-phase-file` option.
|
||||
To set response phase hook, use :option:`--response-phase-file`
|
||||
option.
|
||||
|
||||
For request and response phase hook, user calls :rb:meth:`Nghttpx.run`
|
||||
with block. The :rb:class:`Nghttpx::Env` is passed to the block.
|
||||
User can can access :rb:class:`Nghttpx::Request` and
|
||||
:rb:class:`Nghttpx::Response` objects via :rb:attr:`Nghttpx::Env#req`
|
||||
and :rb:attr:`Nghttpx::Env#resp` respectively.
|
||||
|
||||
.. rb:module:: Nghttpx
|
||||
|
||||
.. rb:classmethod:: run(&block)
|
||||
|
||||
Run request or response phase hook with given *block*.
|
||||
:rb:class:`Nghttpx::Env` object is passed to the given block.
|
||||
|
||||
.. rb:const:: REQUEST_PHASE
|
||||
|
||||
Constant to represent request phase.
|
||||
|
||||
.. rb:const:: RESPONSE_PHASE
|
||||
|
||||
Constant to represent response phase.
|
||||
|
||||
.. rb:class:: Env
|
||||
|
||||
Object to represent current request specific context.
|
||||
|
||||
.. rb:attr_reader:: req
|
||||
|
||||
Return :rb:class:`Request` object.
|
||||
|
||||
.. rb:attr_reader:: resp
|
||||
|
||||
Return :rb:class:`Response` object.
|
||||
|
||||
.. rb:attr_reader:: ctx
|
||||
|
||||
Return Ruby hash object. It persists until request finishes.
|
||||
So values set in request phase hoo can be retrieved in
|
||||
response phase hook.
|
||||
|
||||
.. rb:attr_reader:: phase
|
||||
|
||||
Return the current phase.
|
||||
|
||||
.. rb:attr_reader:: remote_addr
|
||||
|
||||
Return IP address of a remote client.
|
||||
|
||||
.. rb:class:: Request
|
||||
|
||||
Object to represent request from client. The modification to
|
||||
Request object is allowed only in request phase hook.
|
||||
|
||||
.. rb:attr_reader:: http_version_major
|
||||
|
||||
Return HTTP major version.
|
||||
|
||||
.. rb:attr_reader:: http_version_minor
|
||||
|
||||
Return HTTP minor version.
|
||||
|
||||
.. rb:attr_accessor:: method
|
||||
|
||||
HTTP method. On assignment, copy of given value is assigned.
|
||||
We don't accept arbitrary method name. We will document them
|
||||
later, but well known methods, like GET, PUT and POST, are all
|
||||
supported.
|
||||
|
||||
.. rb:attr_accessor:: authority
|
||||
|
||||
Authority (i.e., example.org), including optional port
|
||||
component . On assignment, copy of given value is assigned.
|
||||
|
||||
.. rb:attr_accessor:: scheme
|
||||
|
||||
Scheme (i.e., http, https). On assignment, copy of given
|
||||
value is assigned.
|
||||
|
||||
.. rb:attr_accessor:: path
|
||||
|
||||
Request path, including query component (i.e., /index.html).
|
||||
On assignment, copy of given value is assigned. The path does
|
||||
not include authority component of URI.
|
||||
|
||||
.. rb:attr_reader:: headers
|
||||
|
||||
Return Ruby hash containing copy of request header fields.
|
||||
Changing values in returned hash does not change request
|
||||
header fields actually used in request processing. Use
|
||||
:rb:meth:`Nghttpx::Request#add_header` or
|
||||
:rb:meth:`Nghttpx::Request#set_header` to change request
|
||||
header fields.
|
||||
|
||||
.. rb:method:: add_header(key, value)
|
||||
|
||||
Add header entry associated with key. The value can be single
|
||||
string or array of string. It does not replace any existing
|
||||
values associated with key.
|
||||
|
||||
.. rb:method:: set_header(key, value)
|
||||
|
||||
Set header entry associated with key. The value can be single
|
||||
string or array of string. It replaces any existing values
|
||||
associated with key.
|
||||
|
||||
.. rb:method:: clear_headers
|
||||
|
||||
Clear all existing request header fields.
|
||||
|
||||
.. rb:method:: push uri
|
||||
|
||||
Initiate to push resource identified by *uri*. Only HTTP/2
|
||||
protocol supports this feature. For the other protocols, this
|
||||
method is noop. *uri* can be absolute URI, absolute path or
|
||||
relative path to the current request. For absolute or
|
||||
relative path, scheme and authority are inherited from the
|
||||
current request. Currently, method is always GET. nghttpx
|
||||
will issue request to backend servers to fulfill this request.
|
||||
The request and response phase hooks will be called for pushed
|
||||
resource as well.
|
||||
|
||||
.. rb:class:: Response
|
||||
|
||||
Object to represent response from backend server.
|
||||
|
||||
.. rb:attr_reader:: http_version_major
|
||||
|
||||
Return HTTP major version.
|
||||
|
||||
.. rb:attr_reader:: http_version_minor
|
||||
|
||||
Return HTTP minor version.
|
||||
|
||||
.. rb:attr_accessor:: status
|
||||
|
||||
HTTP status code. It must be in the range [200, 999],
|
||||
inclusive. The non-final status code is not supported in
|
||||
mruby scripting at the moment.
|
||||
|
||||
.. rb:attr_reader:: headers
|
||||
|
||||
Return Ruby hash containing copy of response header fields.
|
||||
Changing values in returned hash does not change response
|
||||
header fields actually used in response processing. Use
|
||||
:rb:meth:`Nghttpx::Response#add_header` or
|
||||
:rb:meth:`Nghttpx::Response#set_header` to change response
|
||||
header fields.
|
||||
|
||||
.. rb:method:: add_header(key, value)
|
||||
|
||||
Add header entry associated with key. The value can be single
|
||||
string or array of string. It does not replace any existing
|
||||
values associated with key.
|
||||
|
||||
.. rb:method:: set_header(key, value)
|
||||
|
||||
Set header entry associated with key. The value can be single
|
||||
string or array of string. It replaces any existing values
|
||||
associated with key.
|
||||
|
||||
.. rb:method:: clear_headers
|
||||
|
||||
Clear all existing response header fields.
|
||||
|
||||
.. rb:method:: return(body)
|
||||
|
||||
Return custom response *body* to a client. When this method
|
||||
is called in request phase hook, the request is not forwarded
|
||||
to the backend, and response phase hook for this request will
|
||||
not be invoked. When this method is called in resonse phase
|
||||
hook, response from backend server is canceled and discarded.
|
||||
The status code and response header fields should be set
|
||||
before using this method. To set status code, use :rb:meth To
|
||||
set response header fields, use
|
||||
:rb:attr:`Nghttpx::Response#status`. If status code is not
|
||||
set, 200 is used. :rb:meth:`Nghttpx::Response#add_header` and
|
||||
:rb:meth:`Nghttpx::Response#set_header`. When this method is
|
||||
invoked in response phase hook, the response headers are
|
||||
filled with the ones received from backend server. To send
|
||||
completely custom header fields, first call
|
||||
:rb:meth:`Nghttpx::Response#clear_headers` to erase all
|
||||
existing header fields, and then add required header fields.
|
||||
It is an error to call this method twice for a given request.
|
||||
|
||||
MRUBY EXAMPLES
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Modify requet path:
|
||||
|
||||
.. code-block:: ruby
|
||||
|
||||
Nghttpx.run do |env|
|
||||
env.req.path = "/apps#{env.req.path}"
|
||||
end
|
||||
|
||||
Note that the file containing the above script must be set with
|
||||
:option:`--request-phase-file` option since we modify request path.
|
||||
|
||||
Restrict permission of viewing a content to a specific client
|
||||
addresses:
|
||||
|
||||
.. code-block:: ruby
|
||||
|
||||
Nghttpx.run do |env|
|
||||
allowed_clients = ["127.0.0.1", "::1"]
|
||||
|
||||
if env.req.path.start_with?("/log/") &&
|
||||
!allowed_clients.include?(env.remote_addr) then
|
||||
env.resp.status = 404
|
||||
env.resp.return "permission denied"
|
||||
end
|
||||
end
|
||||
|
||||
SEE ALSO
|
||||
--------
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ HEADERS = [
|
||||
"accept-language",
|
||||
"cache-control",
|
||||
"user-agent",
|
||||
"date",
|
||||
# disallowed h1 headers
|
||||
'connection',
|
||||
'keep-alive',
|
||||
|
||||
@@ -98,6 +98,9 @@ OPTIONS = [
|
||||
"tls-ticket-key-memcached-interval",
|
||||
"tls-ticket-key-memcached-max-retry",
|
||||
"tls-ticket-key-memcached-max-fail",
|
||||
"request-phase-file",
|
||||
"response-phase-file",
|
||||
"accept-proxy-protocol",
|
||||
"conf",
|
||||
]
|
||||
|
||||
|
||||
@@ -30,7 +30,10 @@ EXTRA_DIST = \
|
||||
server.crt \
|
||||
alt-server.key \
|
||||
alt-server.crt \
|
||||
setenv
|
||||
setenv \
|
||||
req-set-header.rb \
|
||||
resp-set-header.rb \
|
||||
return.rb
|
||||
|
||||
itprep-local:
|
||||
go get -d -v github.com/bradfitz/http2
|
||||
|
||||
@@ -355,6 +355,120 @@ func TestH1H1Websocket(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestH1H1ReqPhaseSetHeader tests mruby request phase hook
|
||||
// modifies request header fields.
|
||||
func TestH1H1ReqPhaseSetHeader(t *testing.T) {
|
||||
st := newServerTester([]string{"--request-phase-file=" + testDir + "/req-set-header.rb"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if got, want := r.Header.Get("User-Agent"), "mruby"; got != want {
|
||||
t.Errorf("User-Agent = %v; want %v", got, want)
|
||||
}
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http1(requestParam{
|
||||
name: "TestH1H1ReqPhaseSetHeader",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http1() = %v", err)
|
||||
}
|
||||
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH1H1ReqPhaseReturn tests mruby request phase hook returns
|
||||
// custom response.
|
||||
func TestH1H1ReqPhaseReturn(t *testing.T) {
|
||||
st := newServerTester([]string{"--request-phase-file=" + testDir + "/return.rb"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("request should not be forwarded")
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http1(requestParam{
|
||||
name: "TestH1H1ReqPhaseReturn",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http1() = %v", err)
|
||||
}
|
||||
|
||||
if got, want := res.status, 404; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
|
||||
hdtests := []struct {
|
||||
k, v string
|
||||
}{
|
||||
{"content-length", "11"},
|
||||
{"from", "mruby"},
|
||||
}
|
||||
for _, tt := range hdtests {
|
||||
if got, want := res.header.Get(tt.k), tt.v; got != want {
|
||||
t.Errorf("%v = %v; want %v", tt.k, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
if got, want := string(res.body), "Hello World"; got != want {
|
||||
t.Errorf("body = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH1H1RespPhaseSetHeader tests mruby response phase hook modifies
|
||||
// response header fields.
|
||||
func TestH1H1RespPhaseSetHeader(t *testing.T) {
|
||||
st := newServerTester([]string{"--response-phase-file=" + testDir + "/resp-set-header.rb"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http1(requestParam{
|
||||
name: "TestH1H1RespPhaseSetHeader",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http1() = %v", err)
|
||||
}
|
||||
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
|
||||
if got, want := res.header.Get("alpha"), "bravo"; got != want {
|
||||
t.Errorf("alpha = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH1H1RespPhaseReturn tests mruby response phase hook returns
|
||||
// custom response.
|
||||
func TestH1H1RespPhaseReturn(t *testing.T) {
|
||||
st := newServerTester([]string{"--response-phase-file=" + testDir + "/return.rb"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http1(requestParam{
|
||||
name: "TestH1H1RespPhaseReturn",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http1() = %v", err)
|
||||
}
|
||||
|
||||
if got, want := res.status, 404; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
|
||||
hdtests := []struct {
|
||||
k, v string
|
||||
}{
|
||||
{"content-length", "11"},
|
||||
{"from", "mruby"},
|
||||
}
|
||||
for _, tt := range hdtests {
|
||||
if got, want := res.header.Get(tt.k), tt.v; got != want {
|
||||
t.Errorf("%v = %v; want %v", tt.k, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
if got, want := string(res.body), "Hello World"; got != want {
|
||||
t.Errorf("body = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH1H2ConnectFailure tests that server handles the situation that
|
||||
// connection attempt to HTTP/2 backend failed.
|
||||
func TestH1H2ConnectFailure(t *testing.T) {
|
||||
@@ -547,3 +661,73 @@ func TestH1H2NoVia(t *testing.T) {
|
||||
t.Errorf("Via: %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH1H2ReqPhaseReturn tests mruby request phase hook returns
|
||||
// custom response.
|
||||
func TestH1H2ReqPhaseReturn(t *testing.T) {
|
||||
st := newServerTester([]string{"--http2-bridge", "--request-phase-file=" + testDir + "/return.rb"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("request should not be forwarded")
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http1(requestParam{
|
||||
name: "TestH1H2ReqPhaseReturn",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http1() = %v", err)
|
||||
}
|
||||
|
||||
if got, want := res.status, 404; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
|
||||
hdtests := []struct {
|
||||
k, v string
|
||||
}{
|
||||
{"content-length", "11"},
|
||||
{"from", "mruby"},
|
||||
}
|
||||
for _, tt := range hdtests {
|
||||
if got, want := res.header.Get(tt.k), tt.v; got != want {
|
||||
t.Errorf("%v = %v; want %v", tt.k, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
if got, want := string(res.body), "Hello World"; got != want {
|
||||
t.Errorf("body = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH1H2RespPhaseReturn tests mruby response phase hook returns
|
||||
// custom response.
|
||||
func TestH1H2RespPhaseReturn(t *testing.T) {
|
||||
st := newServerTester([]string{"--http2-bridge", "--response-phase-file=" + testDir + "/return.rb"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http1(requestParam{
|
||||
name: "TestH1H2RespPhaseReturn",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http1() = %v", err)
|
||||
}
|
||||
|
||||
if got, want := res.status, 404; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
|
||||
hdtests := []struct {
|
||||
k, v string
|
||||
}{
|
||||
{"content-length", "11"},
|
||||
{"from", "mruby"},
|
||||
}
|
||||
for _, tt := range hdtests {
|
||||
if got, want := res.header.Get(tt.k), tt.v; got != want {
|
||||
t.Errorf("%v = %v; want %v", tt.k, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
if got, want := string(res.body), "Hello World"; got != want {
|
||||
t.Errorf("body = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -640,6 +640,120 @@ func TestH2H1HeaderFields(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1ReqPhaseSetHeader tests mruby request phase hook
|
||||
// modifies request header fields.
|
||||
func TestH2H1ReqPhaseSetHeader(t *testing.T) {
|
||||
st := newServerTester([]string{"--request-phase-file=" + testDir + "/req-set-header.rb"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if got, want := r.Header.Get("User-Agent"), "mruby"; got != want {
|
||||
t.Errorf("User-Agent = %v; want %v", got, want)
|
||||
}
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H1ReqPhaseSetHeader",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1ReqPhaseReturn tests mruby request phase hook returns
|
||||
// custom response.
|
||||
func TestH2H1ReqPhaseReturn(t *testing.T) {
|
||||
st := newServerTester([]string{"--request-phase-file=" + testDir + "/return.rb"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("request should not be forwarded")
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H1ReqPhaseReturn",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
|
||||
if got, want := res.status, 404; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
|
||||
hdtests := []struct {
|
||||
k, v string
|
||||
}{
|
||||
{"content-length", "11"},
|
||||
{"from", "mruby"},
|
||||
}
|
||||
for _, tt := range hdtests {
|
||||
if got, want := res.header.Get(tt.k), tt.v; got != want {
|
||||
t.Errorf("%v = %v; want %v", tt.k, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
if got, want := string(res.body), "Hello World"; got != want {
|
||||
t.Errorf("body = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1RespPhaseSetHeader tests mruby response phase hook modifies
|
||||
// response header fields.
|
||||
func TestH2H1RespPhaseSetHeader(t *testing.T) {
|
||||
st := newServerTester([]string{"--response-phase-file=" + testDir + "/resp-set-header.rb"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H1RespPhaseSetHeader",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
|
||||
if got, want := res.header.Get("alpha"), "bravo"; got != want {
|
||||
t.Errorf("alpha = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1RespPhaseReturn tests mruby response phase hook returns
|
||||
// custom response.
|
||||
func TestH2H1RespPhaseReturn(t *testing.T) {
|
||||
st := newServerTester([]string{"--response-phase-file=" + testDir + "/return.rb"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H1RespPhaseReturn",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
|
||||
if got, want := res.status, 404; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
|
||||
hdtests := []struct {
|
||||
k, v string
|
||||
}{
|
||||
{"content-length", "11"},
|
||||
{"from", "mruby"},
|
||||
}
|
||||
for _, tt := range hdtests {
|
||||
if got, want := res.header.Get(tt.k), tt.v; got != want {
|
||||
t.Errorf("%v = %v; want %v", tt.k, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
if got, want := string(res.body), "Hello World"; got != want {
|
||||
t.Errorf("body = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1Upgrade tests HTTP Upgrade to HTTP/2
|
||||
func TestH2H1Upgrade(t *testing.T) {
|
||||
st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {})
|
||||
@@ -673,6 +787,362 @@ func TestH2H1Upgrade(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1ProxyProtocolV1TCP4 tests PROXY protocol version 1
|
||||
// containing TCP4 entry is accepted and X-Forwarded-For contains
|
||||
// advertised src address.
|
||||
func TestH2H1ProxyProtocolV1TCP4(t *testing.T) {
|
||||
st := newServerTester([]string{"--accept-proxy-protocol", "--add-x-forwarded-for"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if got, want := r.Header.Get("X-Forwarded-For"), "192.168.0.2"; got != want {
|
||||
t.Errorf("X-Forwarded-For: %v; want %v", got, want)
|
||||
}
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
st.conn.Write([]byte("PROXY TCP4 192.168.0.2 192.168.0.100 12345 8080\r\n"))
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H1ProxyProtocolV1TCP4",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("res.status: %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1ProxyProtocolV1TCP6 tests PROXY protocol version 1
|
||||
// containing TCP6 entry is accepted and X-Forwarded-For contains
|
||||
// advertised src address.
|
||||
func TestH2H1ProxyProtocolV1TCP6(t *testing.T) {
|
||||
st := newServerTester([]string{"--accept-proxy-protocol", "--add-x-forwarded-for"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if got, want := r.Header.Get("X-Forwarded-For"), "2001:0db8:85a3:0000:0000:8a2e:0370:7334"; got != want {
|
||||
t.Errorf("X-Forwarded-For: %v; want %v", got, want)
|
||||
}
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
st.conn.Write([]byte("PROXY TCP6 2001:0db8:85a3:0000:0000:8a2e:0370:7334 ::1 12345 8080\r\n"))
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H1ProxyProtocolV1TCP6",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("res.status: %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1ProxyProtocolV1Unknown tests PROXY protocol version 1
|
||||
// containing UNKNOWN entry is accepted.
|
||||
func TestH2H1ProxyProtocolV1Unknown(t *testing.T) {
|
||||
st := newServerTester([]string{"--accept-proxy-protocol", "--add-x-forwarded-for"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if got, notWant := r.Header.Get("X-Forwarded-For"), "192.168.0.2"; got == notWant {
|
||||
t.Errorf("X-Forwarded-For: %v")
|
||||
}
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
st.conn.Write([]byte("PROXY UNKNOWN 192.168.0.2 192.168.0.100 12345 8080\r\n"))
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H1ProxyProtocolV1Unknown",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("res.status: %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1ProxyProtocolV1JustUnknown tests PROXY protocol version 1
|
||||
// containing only "PROXY UNKNOWN" is accepted.
|
||||
func TestH2H1ProxyProtocolV1JustUnknown(t *testing.T) {
|
||||
st := newServerTester([]string{"--accept-proxy-protocol", "--add-x-forwarded-for"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
st.conn.Write([]byte("PROXY UNKNOWN\r\n"))
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H1ProxyProtocolV1JustUnknown",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("res.status: %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1ProxyProtocolV1TooLongLine tests PROXY protocol version 1
|
||||
// line longer than 107 bytes must be rejected
|
||||
func TestH2H1ProxyProtocolV1TooLongLine(t *testing.T) {
|
||||
st := newServerTester([]string{"--accept-proxy-protocol", "--add-x-forwarded-for"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
st.conn.Write([]byte("PROXY UNKNOWN ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff 65535 655350\r\n"))
|
||||
|
||||
_, err := st.http2(requestParam{
|
||||
name: "TestH2H1ProxyProtocolV1TooLongLine",
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("connection was not terminated")
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1ProxyProtocolV1BadLineEnd tests that PROXY protocol version
|
||||
// 1 line ending without \r\n should be rejected.
|
||||
func TestH2H1ProxyProtocolV1BadLineEnd(t *testing.T) {
|
||||
st := newServerTester([]string{"--accept-proxy-protocol"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
st.conn.Write([]byte("PROXY TCP6 ::1 ::1 12345 8080\r \n"))
|
||||
|
||||
_, err := st.http2(requestParam{
|
||||
name: "TestH2H1ProxyProtocolV1BadLineEnd",
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("connection was not terminated")
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1ProxyProtocolV1NoEnd tests that PROXY protocol version 1
|
||||
// line containing no \r\n should be rejected.
|
||||
func TestH2H1ProxyProtocolV1NoEnd(t *testing.T) {
|
||||
st := newServerTester([]string{"--accept-proxy-protocol"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
st.conn.Write([]byte("PROXY TCP6 ::1 ::1 12345 8080"))
|
||||
|
||||
_, err := st.http2(requestParam{
|
||||
name: "TestH2H1ProxyProtocolV1NoEnd",
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("connection was not terminated")
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1ProxyProtocolV1EmbeddedNULL tests that PROXY protocol
|
||||
// version 1 line containing NULL character should be rejected.
|
||||
func TestH2H1ProxyProtocolV1EmbeddedNULL(t *testing.T) {
|
||||
st := newServerTester([]string{"--accept-proxy-protocol"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
b := []byte("PROXY TCP6 ::1*foo ::1 12345 8080\r\n")
|
||||
b[14] = 0
|
||||
st.conn.Write(b)
|
||||
|
||||
_, err := st.http2(requestParam{
|
||||
name: "TestH2H1ProxyProtocolV1EmbeddedNULL",
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("connection was not terminated")
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1ProxyProtocolV1MissingSrcPort tests that PROXY protocol
|
||||
// version 1 line without src port should be rejected.
|
||||
func TestH2H1ProxyProtocolV1MissingSrcPort(t *testing.T) {
|
||||
st := newServerTester([]string{"--accept-proxy-protocol"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
st.conn.Write([]byte("PROXY TCP6 ::1 ::1 8080\r\n"))
|
||||
|
||||
_, err := st.http2(requestParam{
|
||||
name: "TestH2H1ProxyProtocolV1MissingSrcPort",
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("connection was not terminated")
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1ProxyProtocolV1MissingDstPort tests that PROXY protocol
|
||||
// version 1 line without dst port should be rejected.
|
||||
func TestH2H1ProxyProtocolV1MissingDstPort(t *testing.T) {
|
||||
st := newServerTester([]string{"--accept-proxy-protocol"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
st.conn.Write([]byte("PROXY TCP6 ::1 ::1 12345 \r\n"))
|
||||
|
||||
_, err := st.http2(requestParam{
|
||||
name: "TestH2H1ProxyProtocolV1MissingDstPort",
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("connection was not terminated")
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1ProxyProtocolV1InvalidSrcPort tests that PROXY protocol
|
||||
// containing invalid src port should be rejected.
|
||||
func TestH2H1ProxyProtocolV1InvalidSrcPort(t *testing.T) {
|
||||
st := newServerTester([]string{"--accept-proxy-protocol"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
st.conn.Write([]byte("PROXY TCP6 ::1 ::1 123x 8080\r\n"))
|
||||
|
||||
_, err := st.http2(requestParam{
|
||||
name: "TestH2H1ProxyProtocolV1InvalidSrcPort",
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("connection was not terminated")
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1ProxyProtocolV1InvalidDstPort tests that PROXY protocol
|
||||
// containing invalid dst port should be rejected.
|
||||
func TestH2H1ProxyProtocolV1InvalidDstPort(t *testing.T) {
|
||||
st := newServerTester([]string{"--accept-proxy-protocol"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
st.conn.Write([]byte("PROXY TCP6 ::1 ::1 123456 80x\r\n"))
|
||||
|
||||
_, err := st.http2(requestParam{
|
||||
name: "TestH2H1ProxyProtocolV1InvalidDstPort",
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("connection was not terminated")
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1ProxyProtocolV1LeadingZeroPort tests that PROXY protocol
|
||||
// version 1 line with non zero port with leading zero should be
|
||||
// rejected.
|
||||
func TestH2H1ProxyProtocolV1LeadingZeroPort(t *testing.T) {
|
||||
st := newServerTester([]string{"--accept-proxy-protocol"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
st.conn.Write([]byte("PROXY TCP6 ::1 ::1 03000 8080\r\n"))
|
||||
|
||||
_, err := st.http2(requestParam{
|
||||
name: "TestH2H1ProxyProtocolV1LeadingZeroPort",
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("connection was not terminated")
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1ProxyProtocolV1TooLargeSrcPort tests that PROXY protocol
|
||||
// containing too large src port should be rejected.
|
||||
func TestH2H1ProxyProtocolV1TooLargeSrcPort(t *testing.T) {
|
||||
st := newServerTester([]string{"--accept-proxy-protocol"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
st.conn.Write([]byte("PROXY TCP6 ::1 ::1 65536 8080\r\n"))
|
||||
|
||||
_, err := st.http2(requestParam{
|
||||
name: "TestH2H1ProxyProtocolV1TooLargeSrcPort",
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("connection was not terminated")
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1ProxyProtocolV1TooLargeDstPort tests that PROXY protocol
|
||||
// containing too large dst port should be rejected.
|
||||
func TestH2H1ProxyProtocolV1TooLargeDstPort(t *testing.T) {
|
||||
st := newServerTester([]string{"--accept-proxy-protocol"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
st.conn.Write([]byte("PROXY TCP6 ::1 ::1 12345 65536\r\n"))
|
||||
|
||||
_, err := st.http2(requestParam{
|
||||
name: "TestH2H1ProxyProtocolV1TooLargeDstPort",
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("connection was not terminated")
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1ProxyProtocolV1InvalidSrcAddr tests that PROXY protocol
|
||||
// containing invalid src addr should be rejected.
|
||||
func TestH2H1ProxyProtocolV1InvalidSrcAddr(t *testing.T) {
|
||||
st := newServerTester([]string{"--accept-proxy-protocol"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
st.conn.Write([]byte("PROXY TCP6 192.168.0.1 ::1 12345 8080\r\n"))
|
||||
|
||||
_, err := st.http2(requestParam{
|
||||
name: "TestH2H1ProxyProtocolV1InvalidSrcAddr",
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("connection was not terminated")
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1ProxyProtocolV1InvalidDstAddr tests that PROXY protocol
|
||||
// containing invalid dst addr should be rejected.
|
||||
func TestH2H1ProxyProtocolV1InvalidDstAddr(t *testing.T) {
|
||||
st := newServerTester([]string{"--accept-proxy-protocol"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
st.conn.Write([]byte("PROXY TCP6 ::1 192.168.0.1 12345 8080\r\n"))
|
||||
|
||||
_, err := st.http2(requestParam{
|
||||
name: "TestH2H1ProxyProtocolV1InvalidDstAddr",
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("connection was not terminated")
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1ProxyProtocolV1InvalidProtoFamily tests that PROXY protocol
|
||||
// containing invalid protocol family should be rejected.
|
||||
func TestH2H1ProxyProtocolV1InvalidProtoFamily(t *testing.T) {
|
||||
st := newServerTester([]string{"--accept-proxy-protocol"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
st.conn.Write([]byte("PROXY UNIX ::1 ::1 12345 8080\r\n"))
|
||||
|
||||
_, err := st.http2(requestParam{
|
||||
name: "TestH2H1ProxyProtocolV1InvalidProtoFamily",
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("connection was not terminated")
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1ProxyProtocolV1InvalidID tests that PROXY protocol
|
||||
// containing invalid PROXY protocol version 1 ID should be rejected.
|
||||
func TestH2H1ProxyProtocolV1InvalidID(t *testing.T) {
|
||||
st := newServerTester([]string{"--accept-proxy-protocol"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
st.conn.Write([]byte("PR0XY TCP6 ::1 ::1 12345 8080\r\n"))
|
||||
|
||||
_, err := st.http2(requestParam{
|
||||
name: "TestH2H1ProxyProtocolV1InvalidID",
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("connection was not terminated")
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1GracefulShutdown tests graceful shutdown.
|
||||
func TestH2H1GracefulShutdown(t *testing.T) {
|
||||
st := newServerTester(nil, t, noopHandler)
|
||||
@@ -875,3 +1345,73 @@ func TestH2H2TLSXfp(t *testing.T) {
|
||||
t.Errorf("res.status: %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H2ReqPhaseReturn tests mruby request phase hook returns
|
||||
// custom response.
|
||||
func TestH2H2ReqPhaseReturn(t *testing.T) {
|
||||
st := newServerTester([]string{"--http2-bridge", "--request-phase-file=" + testDir + "/return.rb"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("request should not be forwarded")
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H2ReqPhaseReturn",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
|
||||
if got, want := res.status, 404; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
|
||||
hdtests := []struct {
|
||||
k, v string
|
||||
}{
|
||||
{"content-length", "11"},
|
||||
{"from", "mruby"},
|
||||
}
|
||||
for _, tt := range hdtests {
|
||||
if got, want := res.header.Get(tt.k), tt.v; got != want {
|
||||
t.Errorf("%v = %v; want %v", tt.k, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
if got, want := string(res.body), "Hello World"; got != want {
|
||||
t.Errorf("body = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H2RespPhaseReturn tests mruby response phase hook returns
|
||||
// custom response.
|
||||
func TestH2H2RespPhaseReturn(t *testing.T) {
|
||||
st := newServerTester([]string{"--http2-bridge", "--response-phase-file=" + testDir + "/return.rb"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H2RespPhaseReturn",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
|
||||
if got, want := res.status, 404; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
|
||||
hdtests := []struct {
|
||||
k, v string
|
||||
}{
|
||||
{"content-length", "11"},
|
||||
{"from", "mruby"},
|
||||
}
|
||||
for _, tt := range hdtests {
|
||||
if got, want := res.header.Get(tt.k), tt.v; got != want {
|
||||
t.Errorf("%v = %v; want %v", tt.k, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
if got, want := string(res.body), "Hello World"; got != want {
|
||||
t.Errorf("body = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,6 +230,120 @@ func TestS3H1InvalidMethod(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestS3H1ReqPhaseSetHeader tests mruby request phase hook
|
||||
// modifies request header fields.
|
||||
func TestS3H1ReqPhaseSetHeader(t *testing.T) {
|
||||
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--request-phase-file=" + testDir + "/req-set-header.rb"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if got, want := r.Header.Get("User-Agent"), "mruby"; got != want {
|
||||
t.Errorf("User-Agent = %v; want %v", got, want)
|
||||
}
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.spdy(requestParam{
|
||||
name: "TestS3H1ReqPhaseSetHeader",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.spdy() = %v", err)
|
||||
}
|
||||
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestS3H1ReqPhaseReturn tests mruby request phase hook returns
|
||||
// custom response.
|
||||
func TestS3H1ReqPhaseReturn(t *testing.T) {
|
||||
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--request-phase-file=" + testDir + "/return.rb"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("request should not be forwarded")
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.spdy(requestParam{
|
||||
name: "TestS3H1ReqPhaseReturn",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.spdy() = %v", err)
|
||||
}
|
||||
|
||||
if got, want := res.status, 404; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
|
||||
hdtests := []struct {
|
||||
k, v string
|
||||
}{
|
||||
{"content-length", "11"},
|
||||
{"from", "mruby"},
|
||||
}
|
||||
for _, tt := range hdtests {
|
||||
if got, want := res.header.Get(tt.k), tt.v; got != want {
|
||||
t.Errorf("%v = %v; want %v", tt.k, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
if got, want := string(res.body), "Hello World"; got != want {
|
||||
t.Errorf("body = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestS3H1RespPhaseSetHeader tests mruby response phase hook modifies
|
||||
// response header fields.
|
||||
func TestS3H1RespPhaseSetHeader(t *testing.T) {
|
||||
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--response-phase-file=" + testDir + "/resp-set-header.rb"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.spdy(requestParam{
|
||||
name: "TestS3H1RespPhaseSetHeader",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.spdy() = %v", err)
|
||||
}
|
||||
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
|
||||
if got, want := res.header.Get("alpha"), "bravo"; got != want {
|
||||
t.Errorf("alpha = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestS3H1RespPhaseReturn tests mruby response phase hook returns
|
||||
// custom response.
|
||||
func TestS3H1RespPhaseReturn(t *testing.T) {
|
||||
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--response-phase-file=" + testDir + "/return.rb"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.spdy(requestParam{
|
||||
name: "TestS3H1RespPhaseReturn",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.spdy() = %v", err)
|
||||
}
|
||||
|
||||
if got, want := res.status, 404; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
|
||||
hdtests := []struct {
|
||||
k, v string
|
||||
}{
|
||||
{"content-length", "11"},
|
||||
{"from", "mruby"},
|
||||
}
|
||||
for _, tt := range hdtests {
|
||||
if got, want := res.header.Get(tt.k), tt.v; got != want {
|
||||
t.Errorf("%v = %v; want %v", tt.k, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
if got, want := string(res.body), "Hello World"; got != want {
|
||||
t.Errorf("body = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestS3H2ConnectFailure tests that server handles the situation that
|
||||
// connection attempt to HTTP/2 backend failed.
|
||||
func TestS3H2ConnectFailure(t *testing.T) {
|
||||
@@ -250,3 +364,73 @@ func TestS3H2ConnectFailure(t *testing.T) {
|
||||
t.Errorf("status: %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestS3H2ReqPhaseReturn tests mruby request phase hook returns
|
||||
// custom response.
|
||||
func TestS3H2ReqPhaseReturn(t *testing.T) {
|
||||
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--http2-bridge", "--request-phase-file=" + testDir + "/return.rb"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("request should not be forwarded")
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.spdy(requestParam{
|
||||
name: "TestS3H2ReqPhaseReturn",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.spdy() = %v", err)
|
||||
}
|
||||
|
||||
if got, want := res.status, 404; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
|
||||
hdtests := []struct {
|
||||
k, v string
|
||||
}{
|
||||
{"content-length", "11"},
|
||||
{"from", "mruby"},
|
||||
}
|
||||
for _, tt := range hdtests {
|
||||
if got, want := res.header.Get(tt.k), tt.v; got != want {
|
||||
t.Errorf("%v = %v; want %v", tt.k, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
if got, want := string(res.body), "Hello World"; got != want {
|
||||
t.Errorf("body = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestS3H2RespPhaseReturn tests mruby response phase hook returns
|
||||
// custom response.
|
||||
func TestS3H2RespPhaseReturn(t *testing.T) {
|
||||
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--http2-bridge", "--response-phase-file=" + testDir + "/return.rb"}, t, noopHandler)
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.spdy(requestParam{
|
||||
name: "TestS3H2RespPhaseReturn",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.spdy() = %v", err)
|
||||
}
|
||||
|
||||
if got, want := res.status, 404; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
|
||||
hdtests := []struct {
|
||||
k, v string
|
||||
}{
|
||||
{"content-length", "11"},
|
||||
{"from", "mruby"},
|
||||
}
|
||||
for _, tt := range hdtests {
|
||||
if got, want := res.header.Get(tt.k), tt.v; got != want {
|
||||
t.Errorf("%v = %v; want %v", tt.k, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
if got, want := string(res.body), "Hello World"; got != want {
|
||||
t.Errorf("body = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
3
integration-tests/req-set-header.rb
Normal file
3
integration-tests/req-set-header.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
Nghttpx.run do |env|
|
||||
env.req.set_header "User-Agent", "mruby"
|
||||
end
|
||||
3
integration-tests/resp-set-header.rb
Normal file
3
integration-tests/resp-set-header.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
Nghttpx.run do |env|
|
||||
env.resp.set_header "Alpha", "bravo"
|
||||
end
|
||||
8
integration-tests/return.rb
Normal file
8
integration-tests/return.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
Nghttpx.run do |env|
|
||||
resp = env.resp
|
||||
|
||||
resp.clear_headers
|
||||
resp.status = 404
|
||||
resp.add_header "from", "mruby"
|
||||
resp.return "Hello World"
|
||||
end
|
||||
@@ -40,11 +40,8 @@
|
||||
|
||||
#define lstreq(A, B, N) ((sizeof((A)) - 1) == (N) && memcmp((A), (B), (N)) == 0)
|
||||
|
||||
#define nghttp2_struct_of(ptr, type, member) \
|
||||
({ \
|
||||
const typeof(((type *)0)->member) *nghttp2__mptr = (ptr); \
|
||||
(type *)(void *)((char *)nghttp2__mptr - __builtin_offsetof(type, member)); \
|
||||
})
|
||||
#define nghttp2_struct_of(ptr, type, member) \
|
||||
((type *)(void *)((char *)(ptr) - offsetof(type, member)))
|
||||
|
||||
/*
|
||||
* Copies 2 byte unsigned integer |n| in host byte order to |buf| in
|
||||
|
||||
@@ -113,7 +113,7 @@ static int stream_subtree_active(nghttp2_stream *stream) {
|
||||
*/
|
||||
static uint64_t stream_next_cycle(nghttp2_stream *stream, uint64_t last_cycle) {
|
||||
return last_cycle +
|
||||
stream->last_writelen * NGHTTP2_MAX_WEIGHT / stream->weight;
|
||||
(stream->last_writelen + 1) * NGHTTP2_MAX_WEIGHT / stream->weight;
|
||||
}
|
||||
|
||||
static int stream_obq_push(nghttp2_stream *dep_stream, nghttp2_stream *stream) {
|
||||
|
||||
@@ -6,6 +6,7 @@ PREV_TAG=$2
|
||||
git checkout refs/tags/$TAG
|
||||
git log --pretty=fuller --date=short refs/tags/$PREV_TAG..HEAD > ChangeLog
|
||||
|
||||
./configure && \
|
||||
git submodule update --init
|
||||
./configure --with-mruby && \
|
||||
make dist-bzip2 && make dist-gzip && make dist-xz || echo "error"
|
||||
make distclean
|
||||
|
||||
@@ -130,12 +130,29 @@ if HAVE_SPDYLAY
|
||||
NGHTTPX_SRCS += shrpx_spdy_upstream.cc shrpx_spdy_upstream.h
|
||||
endif # HAVE_SPDYLAY
|
||||
|
||||
if HAVE_MRUBY
|
||||
NGHTTPX_SRCS += \
|
||||
shrpx_mruby.cc shrpx_mruby.h \
|
||||
shrpx_mruby_module.cc shrpx_mruby_module.h \
|
||||
shrpx_mruby_module_env.cc shrpx_mruby_module_env.h \
|
||||
shrpx_mruby_module_request.cc shrpx_mruby_module_request.h \
|
||||
shrpx_mruby_module_response.cc shrpx_mruby_module_response.h
|
||||
endif # HAVE_MRUBY
|
||||
|
||||
noinst_LIBRARIES = libnghttpx.a
|
||||
libnghttpx_a_SOURCES = ${NGHTTPX_SRCS}
|
||||
libnghttpx_a_CPPFLAGS = ${AM_CPPFLAGS}
|
||||
|
||||
nghttpx_SOURCES = shrpx.cc shrpx.h
|
||||
nghttpx_CPPFLAGS = ${libnghttpx_a_CPPFLAGS}
|
||||
nghttpx_LDADD = libnghttpx.a ${LDADD}
|
||||
|
||||
if HAVE_MRUBY
|
||||
libnghttpx_a_CPPFLAGS += \
|
||||
-I${top_srcdir}/third-party/mruby/include @LIBMRUBY_CFLAGS@
|
||||
nghttpx_LDADD += -L${top_builddir}/third-party/mruby/build/lib @LIBMRUBY_LIBS@
|
||||
endif # HAVE_MRUBY
|
||||
|
||||
if HAVE_CUNIT
|
||||
check_PROGRAMS += nghttpx-unittest
|
||||
nghttpx_unittest_SOURCES = shrpx-unittest.cc \
|
||||
@@ -148,10 +165,17 @@ nghttpx_unittest_SOURCES = shrpx-unittest.cc \
|
||||
nghttp2_gzip.c nghttp2_gzip.h \
|
||||
buffer_test.cc buffer_test.h \
|
||||
memchunk_test.cc memchunk_test.h
|
||||
nghttpx_unittest_CPPFLAGS = ${AM_CPPFLAGS}\
|
||||
nghttpx_unittest_CPPFLAGS = ${AM_CPPFLAGS} \
|
||||
-DNGHTTP2_TESTS_DIR=\"$(top_srcdir)/tests\"
|
||||
nghttpx_unittest_LDADD = libnghttpx.a ${LDADD} @CUNIT_LIBS@ @TESTLDADD@
|
||||
|
||||
if HAVE_MRUBY
|
||||
nghttpx_unittest_CPPFLAGS += \
|
||||
-I${top_srcdir}/third-party/mruby/include @LIBMRUBY_CFLAGS@
|
||||
nghttpx_unittest_LDADD += \
|
||||
-L${top_builddir}/third-party/mruby/build/lib @LIBMRUBY_LIBS@
|
||||
endif # HAVE_MRUBY
|
||||
|
||||
TESTS += nghttpx-unittest
|
||||
endif # HAVE_CUNIT
|
||||
|
||||
|
||||
@@ -265,10 +265,7 @@ Client::Client(Worker *worker, size_t req_todo)
|
||||
request_timeout_watcher.data = this;
|
||||
}
|
||||
|
||||
Client::~Client() {
|
||||
ev_timer_stop(worker->loop, &request_timeout_watcher);
|
||||
disconnect();
|
||||
}
|
||||
Client::~Client() { disconnect(); }
|
||||
|
||||
int Client::do_read() { return readfn(*this); }
|
||||
int Client::do_write() { return writefn(*this); }
|
||||
@@ -348,7 +345,7 @@ void Client::fail() {
|
||||
void Client::disconnect() {
|
||||
ev_timer_stop(worker->loop, &conn_inactivity_watcher);
|
||||
ev_timer_stop(worker->loop, &conn_active_watcher);
|
||||
|
||||
ev_timer_stop(worker->loop, &request_timeout_watcher);
|
||||
streams.clear();
|
||||
session.reset();
|
||||
state = CLIENT_IDLE;
|
||||
@@ -662,11 +659,6 @@ int Client::read_clear() {
|
||||
if (on_read(buf, nread) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!first_byte_received) {
|
||||
first_byte_received = true;
|
||||
record_ttfb(&worker->stats);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -789,11 +781,6 @@ int Client::read_tls() {
|
||||
if (on_read(buf, rv) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!first_byte_received) {
|
||||
first_byte_received = true;
|
||||
record_ttfb(&worker->stats);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -852,8 +839,13 @@ void Client::record_connect_time(Stats *stat) {
|
||||
stat->connect_times.push_back(std::chrono::steady_clock::now());
|
||||
}
|
||||
|
||||
void Client::record_ttfb(Stats *stat) {
|
||||
stat->ttfbs.push_back(std::chrono::steady_clock::now());
|
||||
void Client::record_ttfb() {
|
||||
if (first_byte_received) {
|
||||
return;
|
||||
}
|
||||
first_byte_received = true;
|
||||
|
||||
worker->stats.ttfbs.push_back(std::chrono::steady_clock::now());
|
||||
}
|
||||
|
||||
void Client::signal_write() { ev_io_start(worker->loop, &wev); }
|
||||
@@ -1164,9 +1156,14 @@ void read_script_from_file(std::istream &infile,
|
||||
char *end;
|
||||
auto v = std::strtod(start, &end);
|
||||
|
||||
if (end == start || errno != 0) {
|
||||
errno = 0;
|
||||
if (v < 0.0 || !std::isfinite(v) || end == start || errno != 0) {
|
||||
auto error = errno;
|
||||
std::cerr << "Time value error at line " << line_count << ". \n\t"
|
||||
<< script_line.substr(0, pos) << std::endl;
|
||||
<< "value = " << script_line.substr(0, pos) << std::endl;
|
||||
if (error != 0) {
|
||||
std::cerr << "\t" << strerror(error) << std::endl;
|
||||
}
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
@@ -1295,15 +1292,15 @@ Options:
|
||||
Path of a file containing one or more lines separated by
|
||||
EOLs. Each script line is composed of two tab-separated
|
||||
fields. The first field represents the time offset from
|
||||
the start of execution, expressed as milliseconds with
|
||||
microsecond resolution. The second field represents the
|
||||
URI. This option will disable URIs getting from
|
||||
command-line. If '-' is given as <PATH>, script lines
|
||||
will be read from stdin. Script lines are used in order
|
||||
for each client. If -n is given, it must be less than
|
||||
or equal to the number of script lines, larger values are
|
||||
clamped to the number of script lines. If -n is
|
||||
not given, the number of requests will default to the
|
||||
the start of execution, expressed as a positive value of
|
||||
milliseconds with microsecond resolution. The second
|
||||
field represents the URI. This option will disable URIs
|
||||
getting from command-line. If '-' is given as <PATH>,
|
||||
script lines will be read from stdin. Script lines are
|
||||
used in order for each client. If -n is given, it must be
|
||||
less than or equal to the number of script lines, larger
|
||||
values are clamped to the number of script lines. If -n
|
||||
is not given, the number of requests will default to the
|
||||
number of script lines. The scheme, host and port defined
|
||||
in the first URI are used solely. Values contained in
|
||||
other URIs, if present, are ignored. Definition of a
|
||||
|
||||
@@ -268,7 +268,7 @@ struct Client {
|
||||
void record_request_time(RequestStat *req_stat);
|
||||
void record_start_time(Stats *stat);
|
||||
void record_connect_time(Stats *stat);
|
||||
void record_ttfb(Stats *stat);
|
||||
void record_ttfb();
|
||||
|
||||
void signal_write();
|
||||
};
|
||||
|
||||
@@ -64,6 +64,9 @@ int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
||||
return 0;
|
||||
}
|
||||
client->worker->stats.bytes_head += frame->hd.length;
|
||||
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
||||
client->record_ttfb();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
@@ -73,6 +76,7 @@ int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
|
||||
int32_t stream_id, const uint8_t *data,
|
||||
size_t len, void *user_data) {
|
||||
auto client = static_cast<Client *>(user_data);
|
||||
client->record_ttfb();
|
||||
client->worker->stats.bytes_body += len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -70,6 +70,10 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
|
||||
reinterpret_cast<const uint8_t *>(value), strlen(value));
|
||||
}
|
||||
client->worker->stats.bytes_head += frame->syn_reply.hd.length;
|
||||
|
||||
if (frame->syn_stream.hd.flags & SPDYLAY_CTRL_FLAG_FIN) {
|
||||
client->record_ttfb();
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -78,6 +82,8 @@ void on_data_chunk_recv_callback(spdylay_session *session, uint8_t flags,
|
||||
int32_t stream_id, const uint8_t *data,
|
||||
size_t len, void *user_data) {
|
||||
auto client = static_cast<Client *>(user_data);
|
||||
|
||||
client->record_ttfb();
|
||||
client->worker->stats.bytes_body += len;
|
||||
|
||||
auto spdy_session = static_cast<SpdySession *>(client->session.get());
|
||||
|
||||
120
src/http2.cc
120
src/http2.cc
@@ -132,13 +132,13 @@ std::string get_status_string(unsigned int status_code) {
|
||||
}
|
||||
}
|
||||
|
||||
void capitalize(std::string &s, size_t offset) {
|
||||
s[offset] = util::upcase(s[offset]);
|
||||
for (size_t i = offset + 1, eoi = s.size(); i < eoi; ++i) {
|
||||
void capitalize(DefaultMemchunks *buf, const std::string &s) {
|
||||
buf->append(util::upcase(s[0]));
|
||||
for (size_t i = 1; i < s.size(); ++i) {
|
||||
if (s[i - 1] == '-') {
|
||||
s[i] = util::upcase(s[i]);
|
||||
buf->append(util::upcase(s[i]));
|
||||
} else {
|
||||
s[i] = util::lowcase(s[i]);
|
||||
buf->append(s[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -242,7 +242,7 @@ void copy_headers_to_nva(std::vector<nghttp2_nv> &nva, const Headers &headers) {
|
||||
}
|
||||
}
|
||||
|
||||
void build_http1_headers_from_headers(std::string &hdrs,
|
||||
void build_http1_headers_from_headers(DefaultMemchunks *buf,
|
||||
const Headers &headers) {
|
||||
for (auto &kv : headers) {
|
||||
if (kv.name.empty() || kv.name[0] == ':') {
|
||||
@@ -262,11 +262,10 @@ void build_http1_headers_from_headers(std::string &hdrs,
|
||||
case HD_X_FORWARDED_PROTO:
|
||||
continue;
|
||||
}
|
||||
hdrs += kv.name;
|
||||
capitalize(hdrs, hdrs.size() - kv.name.size());
|
||||
hdrs += ": ";
|
||||
hdrs += kv.value;
|
||||
hdrs += "\r\n";
|
||||
capitalize(buf, kv.name);
|
||||
buf->append(": ");
|
||||
buf->append(kv.value);
|
||||
buf->append("\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,6 +413,11 @@ int lookup_token(const uint8_t *name, size_t namelen) {
|
||||
break;
|
||||
case 4:
|
||||
switch (name[3]) {
|
||||
case 'e':
|
||||
if (util::streq_l("dat", name, 3)) {
|
||||
return HD_DATE;
|
||||
}
|
||||
break;
|
||||
case 'k':
|
||||
if (util::streq_l("lin", name, 3)) {
|
||||
return HD_LINK;
|
||||
@@ -664,6 +668,15 @@ const Headers::value_type *get_header(const HeaderIndex &hdidx, int16_t token,
|
||||
return &nva[i];
|
||||
}
|
||||
|
||||
Headers::value_type *get_header(const HeaderIndex &hdidx, int16_t token,
|
||||
Headers &nva) {
|
||||
auto i = hdidx[token];
|
||||
if (i == -1) {
|
||||
return nullptr;
|
||||
}
|
||||
return &nva[i];
|
||||
}
|
||||
|
||||
namespace {
|
||||
template <typename InputIt> InputIt skip_lws(InputIt first, InputIt last) {
|
||||
for (; first != last; ++first) {
|
||||
@@ -1299,6 +1312,91 @@ const char *to_method_string(int method_token) {
|
||||
return http_method_str(static_cast<http_method>(method_token));
|
||||
}
|
||||
|
||||
int get_pure_path_component(const char **base, size_t *baselen,
|
||||
const std::string &uri) {
|
||||
int rv;
|
||||
|
||||
http_parser_url u{};
|
||||
rv = http_parser_parse_url(uri.c_str(), uri.size(), 0, &u);
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (u.field_set & (1 << UF_PATH)) {
|
||||
auto &f = u.field_data[UF_PATH];
|
||||
*base = uri.c_str() + f.off;
|
||||
*baselen = f.len;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
*base = "/";
|
||||
*baselen = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int construct_push_component(std::string &scheme, std::string &authority,
|
||||
std::string &path, const char *base,
|
||||
size_t baselen, const char *uri, size_t len) {
|
||||
int rv;
|
||||
const char *rel, *relq = nullptr;
|
||||
size_t rellen, relqlen = 0;
|
||||
|
||||
http_parser_url u{};
|
||||
|
||||
rv = http_parser_parse_url(uri, len, 0, &u);
|
||||
|
||||
if (rv != 0) {
|
||||
if (uri[0] == '/') {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// treat link_url as relative URI.
|
||||
auto end = std::find(uri, uri + len, '#');
|
||||
auto q = std::find(uri, end, '?');
|
||||
|
||||
rel = uri;
|
||||
rellen = q - uri;
|
||||
if (q != end) {
|
||||
relq = q + 1;
|
||||
relqlen = end - relq;
|
||||
}
|
||||
} else {
|
||||
if (u.field_set & (1 << UF_SCHEMA)) {
|
||||
http2::copy_url_component(scheme, &u, UF_SCHEMA, uri);
|
||||
}
|
||||
|
||||
if (u.field_set & (1 << UF_HOST)) {
|
||||
http2::copy_url_component(authority, &u, UF_HOST, uri);
|
||||
if (u.field_set & (1 << UF_PORT)) {
|
||||
authority += ":";
|
||||
authority += util::utos(u.port);
|
||||
}
|
||||
}
|
||||
|
||||
if (u.field_set & (1 << UF_PATH)) {
|
||||
auto &f = u.field_data[UF_PATH];
|
||||
rel = uri + f.off;
|
||||
rellen = f.len;
|
||||
} else {
|
||||
rel = "/";
|
||||
rellen = 1;
|
||||
}
|
||||
|
||||
if (u.field_set & (1 << UF_QUERY)) {
|
||||
auto &f = u.field_data[UF_QUERY];
|
||||
relq = uri + f.off;
|
||||
relqlen = f.len;
|
||||
}
|
||||
}
|
||||
|
||||
path =
|
||||
http2::path_join(base, baselen, nullptr, 0, rel, rellen, relq, relqlen);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace http2
|
||||
|
||||
} // namespace nghttp2
|
||||
|
||||
26
src/http2.h
26
src/http2.h
@@ -38,6 +38,7 @@
|
||||
#include "http-parser/http_parser.h"
|
||||
|
||||
#include "util.h"
|
||||
#include "memchunk.h"
|
||||
|
||||
namespace nghttp2 {
|
||||
|
||||
@@ -69,7 +70,7 @@ namespace http2 {
|
||||
|
||||
std::string get_status_string(unsigned int status_code);
|
||||
|
||||
void capitalize(std::string &s, size_t offset);
|
||||
void capitalize(DefaultMemchunks *buf, const std::string &s);
|
||||
|
||||
// Returns true if |value| is LWS
|
||||
bool lws(const char *value);
|
||||
@@ -137,11 +138,11 @@ nghttp2_nv make_nv_ls(const char (&name)[N], const std::string &value) {
|
||||
// which require special handling (i.e. via), are not copied.
|
||||
void copy_headers_to_nva(std::vector<nghttp2_nv> &nva, const Headers &headers);
|
||||
|
||||
// Appends HTTP/1.1 style header lines to |hdrs| from headers in
|
||||
// Appends HTTP/1.1 style header lines to |buf| from headers in
|
||||
// |headers|. |headers| must be indexed before this call (its
|
||||
// element's token field is assigned). Certain headers, which
|
||||
// requires special handling (i.e. via and cookie), are not appended.
|
||||
void build_http1_headers_from_headers(std::string &hdrs,
|
||||
void build_http1_headers_from_headers(DefaultMemchunks *buf,
|
||||
const Headers &headers);
|
||||
|
||||
// Return positive window_size_increment if WINDOW_UPDATE should be
|
||||
@@ -207,6 +208,7 @@ enum {
|
||||
HD_CONNECTION,
|
||||
HD_CONTENT_LENGTH,
|
||||
HD_COOKIE,
|
||||
HD_DATE,
|
||||
HD_EXPECT,
|
||||
HD_HOST,
|
||||
HD_HTTP2_SETTINGS,
|
||||
@@ -262,6 +264,9 @@ bool http2_mandatory_request_headers_presence(const HeaderIndex &hdidx);
|
||||
const Headers::value_type *get_header(const HeaderIndex &hdidx, int16_t token,
|
||||
const Headers &nva);
|
||||
|
||||
Headers::value_type *get_header(const HeaderIndex &hdidx, int16_t token,
|
||||
Headers &nva);
|
||||
|
||||
struct LinkHeader {
|
||||
// The region of URI is [uri.first, uri.second).
|
||||
std::pair<const char *, const char *> uri;
|
||||
@@ -349,6 +354,21 @@ std::string rewrite_clean_path(InputIt first, InputIt last) {
|
||||
return path;
|
||||
}
|
||||
|
||||
// Stores path component of |uri| in *base. Its extracted length is
|
||||
// stored in *baselen. The extracted path does not include query
|
||||
// component. This function returns 0 if it succeeds, or -1.
|
||||
int get_pure_path_component(const char **base, size_t *baselen,
|
||||
const std::string &uri);
|
||||
|
||||
// Deduces scheme, authority and path from given |uri| of length
|
||||
// |len|, and stores them in |scheme|, |authority|, and |path|
|
||||
// respectively. If |uri| is relative path, path resolution is taken
|
||||
// palce using path given in |base| of length |baselen|. This
|
||||
// function returns 0 if it succeeds, or -1.
|
||||
int construct_push_component(std::string &scheme, std::string &authority,
|
||||
std::string &path, const char *base,
|
||||
size_t baselen, const char *uri, size_t len);
|
||||
|
||||
} // namespace http2
|
||||
|
||||
} // namespace nghttp2
|
||||
|
||||
@@ -167,17 +167,19 @@ void test_http2_copy_headers_to_nva(void) {
|
||||
}
|
||||
|
||||
void test_http2_build_http1_headers_from_headers(void) {
|
||||
std::string hdrs;
|
||||
http2::build_http1_headers_from_headers(hdrs, headers);
|
||||
CU_ASSERT(hdrs == "Alpha: 0\r\n"
|
||||
"Bravo: 1\r\n"
|
||||
"Delta: 4\r\n"
|
||||
"Expect: 5\r\n"
|
||||
"Foxtrot: 6\r\n"
|
||||
"Tango: 7\r\n"
|
||||
"Te: 8\r\n"
|
||||
"Te: 9\r\n"
|
||||
"Zulu: 12\r\n");
|
||||
MemchunkPool pool;
|
||||
DefaultMemchunks buf(&pool);
|
||||
http2::build_http1_headers_from_headers(&buf, headers);
|
||||
auto hdrs = std::string(buf.head->pos, buf.head->last);
|
||||
CU_ASSERT("Alpha: 0\r\n"
|
||||
"Bravo: 1\r\n"
|
||||
"Delta: 4\r\n"
|
||||
"Expect: 5\r\n"
|
||||
"Foxtrot: 6\r\n"
|
||||
"Tango: 7\r\n"
|
||||
"Te: 8\r\n"
|
||||
"Te: 9\r\n"
|
||||
"Zulu: 12\r\n" == hdrs);
|
||||
}
|
||||
|
||||
void test_http2_lws(void) {
|
||||
@@ -880,4 +882,104 @@ void test_http2_rewrite_clean_path(void) {
|
||||
CU_ASSERT(src == http2::rewrite_clean_path(std::begin(src), std::end(src)));
|
||||
}
|
||||
|
||||
void test_http2_get_pure_path_component(void) {
|
||||
const char *base;
|
||||
size_t len;
|
||||
std::string path;
|
||||
|
||||
path = "/";
|
||||
CU_ASSERT(0 == http2::get_pure_path_component(&base, &len, path));
|
||||
CU_ASSERT(util::streq_l("/", base, len));
|
||||
|
||||
path = "/foo";
|
||||
CU_ASSERT(0 == http2::get_pure_path_component(&base, &len, path));
|
||||
CU_ASSERT(util::streq_l("/foo", base, len));
|
||||
|
||||
path = "https://example.org/bar";
|
||||
CU_ASSERT(0 == http2::get_pure_path_component(&base, &len, path));
|
||||
CU_ASSERT(util::streq_l("/bar", base, len));
|
||||
|
||||
path = "https://example.org/alpha?q=a";
|
||||
CU_ASSERT(0 == http2::get_pure_path_component(&base, &len, path));
|
||||
CU_ASSERT(util::streq_l("/alpha", base, len));
|
||||
|
||||
path = "https://example.org/bravo?q=a#fragment";
|
||||
CU_ASSERT(0 == http2::get_pure_path_component(&base, &len, path));
|
||||
CU_ASSERT(util::streq_l("/bravo", base, len));
|
||||
|
||||
path = "\x01\x02";
|
||||
CU_ASSERT(-1 == http2::get_pure_path_component(&base, &len, path));
|
||||
}
|
||||
|
||||
void test_http2_construct_push_component(void) {
|
||||
const char *base;
|
||||
size_t baselen;
|
||||
std::string uri;
|
||||
std::string scheme, authority, path;
|
||||
|
||||
base = "/b/";
|
||||
baselen = 3;
|
||||
|
||||
uri = "https://example.org/foo";
|
||||
|
||||
CU_ASSERT(0 == http2::construct_push_component(scheme, authority, path, base,
|
||||
baselen, uri.c_str(),
|
||||
uri.size()));
|
||||
CU_ASSERT("https" == scheme);
|
||||
CU_ASSERT("example.org" == authority);
|
||||
CU_ASSERT("/foo" == path);
|
||||
|
||||
scheme.clear();
|
||||
authority.clear();
|
||||
path.clear();
|
||||
|
||||
uri = "/foo/bar?q=a";
|
||||
|
||||
CU_ASSERT(0 == http2::construct_push_component(scheme, authority, path, base,
|
||||
baselen, uri.c_str(),
|
||||
uri.size()));
|
||||
CU_ASSERT("" == scheme);
|
||||
CU_ASSERT("" == authority);
|
||||
CU_ASSERT("/foo/bar?q=a" == path);
|
||||
|
||||
scheme.clear();
|
||||
authority.clear();
|
||||
path.clear();
|
||||
|
||||
uri = "foo/../bar?q=a";
|
||||
|
||||
CU_ASSERT(0 == http2::construct_push_component(scheme, authority, path, base,
|
||||
baselen, uri.c_str(),
|
||||
uri.size()));
|
||||
CU_ASSERT("" == scheme);
|
||||
CU_ASSERT("" == authority);
|
||||
CU_ASSERT("/b/bar?q=a" == path);
|
||||
|
||||
scheme.clear();
|
||||
authority.clear();
|
||||
path.clear();
|
||||
|
||||
uri = "";
|
||||
|
||||
CU_ASSERT(0 == http2::construct_push_component(scheme, authority, path, base,
|
||||
baselen, uri.c_str(),
|
||||
uri.size()));
|
||||
CU_ASSERT("" == scheme);
|
||||
CU_ASSERT("" == authority);
|
||||
CU_ASSERT("/" == path);
|
||||
|
||||
scheme.clear();
|
||||
authority.clear();
|
||||
path.clear();
|
||||
|
||||
uri = "?q=a";
|
||||
|
||||
CU_ASSERT(0 == http2::construct_push_component(scheme, authority, path, base,
|
||||
baselen, uri.c_str(),
|
||||
uri.size()));
|
||||
CU_ASSERT("" == scheme);
|
||||
CU_ASSERT("" == authority);
|
||||
CU_ASSERT("/b/?q=a" == path);
|
||||
}
|
||||
|
||||
} // namespace shrpx
|
||||
|
||||
@@ -47,6 +47,8 @@ void test_http2_parse_link_header(void);
|
||||
void test_http2_path_join(void);
|
||||
void test_http2_normalize_path(void);
|
||||
void test_http2_rewrite_clean_path(void);
|
||||
void test_http2_get_pure_path_component(void);
|
||||
void test_http2_construct_push_component(void);
|
||||
|
||||
} // namespace shrpx
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include <memory>
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
#include "template.h"
|
||||
|
||||
@@ -126,6 +127,17 @@ template <typename Memchunk> struct Memchunks {
|
||||
m = next;
|
||||
}
|
||||
}
|
||||
size_t append(char c) {
|
||||
if (!tail) {
|
||||
head = tail = pool->get();
|
||||
} else if (tail->left() == 0) {
|
||||
tail->next = pool->get();
|
||||
tail = tail->next;
|
||||
}
|
||||
*tail->last++ = c;
|
||||
++len;
|
||||
return 1;
|
||||
}
|
||||
size_t append(const void *src, size_t count) {
|
||||
if (count == 0) {
|
||||
return 0;
|
||||
@@ -156,6 +168,7 @@ template <typename Memchunk> struct Memchunks {
|
||||
template <size_t N> size_t append(const char (&s)[N]) {
|
||||
return append(s, N - 1);
|
||||
}
|
||||
size_t append(const std::string &s) { return append(s.c_str(), s.size()); }
|
||||
size_t remove(void *dest, size_t count) {
|
||||
if (!tail || count == 0) {
|
||||
return 0;
|
||||
|
||||
@@ -100,6 +100,10 @@ int main(int argc, char *argv[]) {
|
||||
shrpx::test_http2_normalize_path) ||
|
||||
!CU_add_test(pSuite, "http2_rewrite_clean_path",
|
||||
shrpx::test_http2_rewrite_clean_path) ||
|
||||
!CU_add_test(pSuite, "http2_get_pure_path_component",
|
||||
shrpx::test_http2_get_pure_path_component) ||
|
||||
!CU_add_test(pSuite, "http2_construct_push_component",
|
||||
shrpx::test_http2_construct_push_component) ||
|
||||
!CU_add_test(pSuite, "downstream_index_request_headers",
|
||||
shrpx::test_downstream_index_request_headers) ||
|
||||
!CU_add_test(pSuite, "downstream_index_response_headers",
|
||||
|
||||
45
src/shrpx.cc
45
src/shrpx.cc
@@ -926,9 +926,13 @@ int event_loop() {
|
||||
#endif // !NOTHREADS
|
||||
|
||||
if (get_config()->num_worker == 1) {
|
||||
conn_handler->create_single_worker();
|
||||
rv = conn_handler->create_single_worker();
|
||||
} else {
|
||||
conn_handler->create_worker_thread(get_config()->num_worker);
|
||||
rv = conn_handler->create_worker_thread(get_config()->num_worker);
|
||||
}
|
||||
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifndef NOTHREADS
|
||||
@@ -1264,6 +1268,8 @@ Connections:
|
||||
timeouts when connecting and making CONNECT request can
|
||||
be specified by --backend-read-timeout and
|
||||
--backend-write-timeout options.
|
||||
--accept-proxy-protocol
|
||||
Accept PROXY protocol version 1 on frontend connection.
|
||||
|
||||
Performance:
|
||||
-n, --workers=<N>
|
||||
@@ -1726,6 +1732,16 @@ Process:
|
||||
Run this program as <USER>. This option is intended to
|
||||
be used to drop root privileges.
|
||||
|
||||
Scripting:
|
||||
--request-phase-file=<PATH>
|
||||
Set mruby script file which will be executed when
|
||||
request header fields are completely received from
|
||||
frontend. This hook is called request phase hook.
|
||||
--response-phase-file=<PATH>
|
||||
Set mruby script file which will be executed when
|
||||
response header fields are completely received from
|
||||
backend. This hook is called response phase hook.
|
||||
|
||||
Misc:
|
||||
--conf=<PATH>
|
||||
Load configuration from <PATH>.
|
||||
@@ -1899,6 +1915,9 @@ int main(int argc, char **argv) {
|
||||
89},
|
||||
{SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL, required_argument, &flag,
|
||||
90},
|
||||
{SHRPX_OPT_REQUEST_PHASE_FILE, required_argument, &flag, 91},
|
||||
{SHRPX_OPT_RESPONSE_PHASE_FILE, required_argument, &flag, 92},
|
||||
{SHRPX_OPT_ACCEPT_PROXY_PROTOCOL, no_argument, &flag, 93},
|
||||
{nullptr, 0, nullptr, 0}};
|
||||
|
||||
int option_index = 0;
|
||||
@@ -1930,11 +1949,7 @@ int main(int argc, char **argv) {
|
||||
cmdcfgs.emplace_back(SHRPX_OPT_INSECURE, "yes");
|
||||
break;
|
||||
case 'n':
|
||||
#ifdef NOTHREADS
|
||||
LOG(WARN) << "Threading disabled at build time, no threads created.";
|
||||
#else
|
||||
cmdcfgs.emplace_back(SHRPX_OPT_WORKERS, optarg);
|
||||
#endif // NOTHREADS
|
||||
break;
|
||||
case 'o':
|
||||
cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_FRAME_DEBUG, "yes");
|
||||
@@ -2296,6 +2311,18 @@ int main(int argc, char **argv) {
|
||||
cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL,
|
||||
optarg);
|
||||
break;
|
||||
case 91:
|
||||
// --request-phase-file
|
||||
cmdcfgs.emplace_back(SHRPX_OPT_REQUEST_PHASE_FILE, optarg);
|
||||
break;
|
||||
case 92:
|
||||
// --response-phase-file
|
||||
cmdcfgs.emplace_back(SHRPX_OPT_RESPONSE_PHASE_FILE, optarg);
|
||||
break;
|
||||
case 93:
|
||||
// --accept-proxy-protocol
|
||||
cmdcfgs.emplace_back(SHRPX_OPT_ACCEPT_PROXY_PROTOCOL, "yes");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -2613,10 +2640,14 @@ int main(int argc, char **argv) {
|
||||
act.sa_handler = SIG_IGN;
|
||||
sigaction(SIGPIPE, &act, nullptr);
|
||||
|
||||
event_loop();
|
||||
if (event_loop() != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOG(NOTICE) << "Shutdown momentarily";
|
||||
|
||||
delete log_config();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -104,6 +104,8 @@ void writecb(struct ev_loop *loop, ev_io *w, int revents) {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int ClientHandler::noop() { return 0; }
|
||||
|
||||
int ClientHandler::read_clear() {
|
||||
ev_timer_again(conn_.loop, &conn_.rt);
|
||||
|
||||
@@ -382,6 +384,17 @@ ClientHandler::ClientHandler(Worker *worker, int fd, SSL *ssl,
|
||||
conn_.rlimit.startw();
|
||||
ev_timer_again(conn_.loop, &conn_.rt);
|
||||
|
||||
if (get_config()->accept_proxy_protocol) {
|
||||
read_ = &ClientHandler::read_clear;
|
||||
write_ = &ClientHandler::noop;
|
||||
on_read_ = &ClientHandler::proxy_protocol_read;
|
||||
on_write_ = &ClientHandler::upstream_noop;
|
||||
} else {
|
||||
setup_upstream_io_callback();
|
||||
}
|
||||
}
|
||||
|
||||
void ClientHandler::setup_upstream_io_callback() {
|
||||
if (conn_.tls.ssl) {
|
||||
conn_.prepare_server_handshake();
|
||||
read_ = write_ = &ClientHandler::tls_handshake;
|
||||
@@ -452,108 +465,96 @@ void ClientHandler::reset_upstream_write_timeout(ev_tstamp t) {
|
||||
int ClientHandler::validate_next_proto() {
|
||||
const unsigned char *next_proto = nullptr;
|
||||
unsigned int next_proto_len;
|
||||
int rv;
|
||||
|
||||
// First set callback for catch all cases
|
||||
on_read_ = &ClientHandler::upstream_read;
|
||||
|
||||
SSL_get0_next_proto_negotiated(conn_.tls.ssl, &next_proto, &next_proto_len);
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
if (next_proto) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
std::string proto(next_proto, next_proto + next_proto_len);
|
||||
CLOG(INFO, this) << "The negotiated next protocol: " << proto;
|
||||
}
|
||||
if (!ssl::in_proto_list(get_config()->npn_list, next_proto,
|
||||
next_proto_len)) {
|
||||
break;
|
||||
}
|
||||
if (util::check_h2_is_selected(next_proto, next_proto_len)) {
|
||||
|
||||
on_read_ = &ClientHandler::upstream_http2_connhd_read;
|
||||
|
||||
auto http2_upstream = make_unique<Http2Upstream>(this);
|
||||
|
||||
if (!nghttp2::ssl::check_http2_requirement(conn_.tls.ssl)) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
LOG(INFO) << "TLSv1.2 was not negotiated. "
|
||||
<< "HTTP/2 must not be negotiated.";
|
||||
}
|
||||
|
||||
rv = http2_upstream->terminate_session(NGHTTP2_INADEQUATE_SECURITY);
|
||||
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
upstream_ = std::move(http2_upstream);
|
||||
alpn_.assign(next_proto, next_proto + next_proto_len);
|
||||
|
||||
// At this point, input buffer is already filled with some
|
||||
// bytes. The read callback is not called until new data
|
||||
// come. So consume input buffer here.
|
||||
if (on_read() != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
} else {
|
||||
#ifdef HAVE_SPDYLAY
|
||||
uint16_t version = spdylay_npn_get_version(next_proto, next_proto_len);
|
||||
if (version) {
|
||||
upstream_ = make_unique<SpdyUpstream>(version, this);
|
||||
|
||||
switch (version) {
|
||||
case SPDYLAY_PROTO_SPDY2:
|
||||
alpn_ = "spdy/2";
|
||||
break;
|
||||
case SPDYLAY_PROTO_SPDY3:
|
||||
alpn_ = "spdy/3";
|
||||
break;
|
||||
case SPDYLAY_PROTO_SPDY3_1:
|
||||
alpn_ = "spdy/3.1";
|
||||
break;
|
||||
default:
|
||||
alpn_ = "spdy/unknown";
|
||||
}
|
||||
|
||||
// At this point, input buffer is already filled with some
|
||||
// bytes. The read callback is not called until new data
|
||||
// come. So consume input buffer here.
|
||||
if (on_read() != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif // HAVE_SPDYLAY
|
||||
if (next_proto_len == 8 && memcmp("http/1.1", next_proto, 8) == 0) {
|
||||
upstream_ = make_unique<HttpsUpstream>(this);
|
||||
alpn_ = "http/1.1";
|
||||
|
||||
// At this point, input buffer is already filled with some
|
||||
// bytes. The read callback is not called until new data
|
||||
// come. So consume input buffer here.
|
||||
if (on_read() != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||
if (next_proto == nullptr) {
|
||||
SSL_get0_alpn_selected(conn_.tls.ssl, &next_proto, &next_proto_len);
|
||||
#else // OPENSSL_VERSION_NUMBER < 0x10002000L
|
||||
break;
|
||||
#endif // OPENSSL_VERSION_NUMBER < 0x10002000L
|
||||
}
|
||||
if (!next_proto) {
|
||||
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||
|
||||
if (next_proto == nullptr) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
CLOG(INFO, this) << "No protocol negotiated. Fallback to HTTP/1.1";
|
||||
}
|
||||
|
||||
upstream_ = make_unique<HttpsUpstream>(this);
|
||||
alpn_ = "http/1.1";
|
||||
|
||||
// At this point, input buffer is already filled with some bytes.
|
||||
// The read callback is not called until new data come. So consume
|
||||
// input buffer here.
|
||||
if (on_read() != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
std::string proto(next_proto, next_proto + next_proto_len);
|
||||
CLOG(INFO, this) << "The negotiated next protocol: " << proto;
|
||||
}
|
||||
|
||||
if (!ssl::in_proto_list(get_config()->npn_list, next_proto, next_proto_len)) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
CLOG(INFO, this) << "The negotiated protocol is not supported";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (util::check_h2_is_selected(next_proto, next_proto_len)) {
|
||||
on_read_ = &ClientHandler::upstream_http2_connhd_read;
|
||||
|
||||
auto http2_upstream = make_unique<Http2Upstream>(this);
|
||||
|
||||
upstream_ = std::move(http2_upstream);
|
||||
alpn_.assign(next_proto, next_proto + next_proto_len);
|
||||
|
||||
// At this point, input buffer is already filled with some bytes.
|
||||
// The read callback is not called until new data come. So consume
|
||||
// input buffer here.
|
||||
if (on_read() != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef HAVE_SPDYLAY
|
||||
auto spdy_version = spdylay_npn_get_version(next_proto, next_proto_len);
|
||||
if (spdy_version) {
|
||||
upstream_ = make_unique<SpdyUpstream>(spdy_version, this);
|
||||
|
||||
switch (spdy_version) {
|
||||
case SPDYLAY_PROTO_SPDY2:
|
||||
alpn_ = "spdy/2";
|
||||
break;
|
||||
case SPDYLAY_PROTO_SPDY3:
|
||||
alpn_ = "spdy/3";
|
||||
break;
|
||||
case SPDYLAY_PROTO_SPDY3_1:
|
||||
alpn_ = "spdy/3.1";
|
||||
break;
|
||||
default:
|
||||
alpn_ = "spdy/unknown";
|
||||
}
|
||||
|
||||
// At this point, input buffer is already filled with some bytes.
|
||||
// The read callback is not called until new data come. So consume
|
||||
// input buffer here.
|
||||
if (on_read() != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif // HAVE_SPDYLAY
|
||||
|
||||
if (next_proto_len == 8 && memcmp("http/1.1", next_proto, 8) == 0) {
|
||||
upstream_ = make_unique<HttpsUpstream>(this);
|
||||
alpn_ = "http/1.1";
|
||||
|
||||
@@ -751,41 +752,23 @@ namespace {
|
||||
// HttpDownstreamConnection::push_request_headers(), but vastly
|
||||
// simplified since we only care about absolute URI.
|
||||
std::string construct_absolute_request_uri(Downstream *downstream) {
|
||||
const char *authority = nullptr, *host = nullptr;
|
||||
if (!downstream->get_request_http2_authority().empty()) {
|
||||
authority = downstream->get_request_http2_authority().c_str();
|
||||
}
|
||||
auto h = downstream->get_request_header(http2::HD_HOST);
|
||||
if (h) {
|
||||
host = h->value.c_str();
|
||||
}
|
||||
if (!authority && !host) {
|
||||
auto &authority = downstream->get_request_http2_authority();
|
||||
if (authority.empty()) {
|
||||
return downstream->get_request_path();
|
||||
}
|
||||
std::string uri;
|
||||
if (downstream->get_request_http2_scheme().empty()) {
|
||||
auto &scheme = downstream->get_request_http2_scheme();
|
||||
if (scheme.empty()) {
|
||||
// We may have to log the request which lacks scheme (e.g.,
|
||||
// http/1.1 with origin form).
|
||||
uri += "http://";
|
||||
} else {
|
||||
uri += downstream->get_request_http2_scheme();
|
||||
uri += scheme;
|
||||
uri += "://";
|
||||
}
|
||||
if (authority) {
|
||||
uri += authority;
|
||||
} else {
|
||||
uri += host;
|
||||
}
|
||||
uri += authority;
|
||||
uri += downstream->get_request_path();
|
||||
|
||||
// Server-wide OPTIONS takes following form in proxy request:
|
||||
//
|
||||
// OPTIONS http://example.org HTTP/1.1
|
||||
//
|
||||
// Notice that no slash after authority. See
|
||||
// http://tools.ietf.org/html/rfc7230#section-5.3.4
|
||||
if (downstream->get_request_path() != "*") {
|
||||
uri += downstream->get_request_path();
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
} // namespace
|
||||
@@ -799,12 +782,15 @@ void ClientHandler::write_accesslog(Downstream *downstream) {
|
||||
downstream, ipaddr_.c_str(),
|
||||
http2::to_method_string(downstream->get_request_method()),
|
||||
|
||||
(downstream->get_request_method() != HTTP_CONNECT &&
|
||||
(get_config()->http2_proxy || get_config()->client_proxy))
|
||||
? construct_absolute_request_uri(downstream).c_str()
|
||||
: downstream->get_request_path().empty()
|
||||
? downstream->get_request_http2_authority().c_str()
|
||||
: downstream->get_request_path().c_str(),
|
||||
downstream->get_request_method() == HTTP_CONNECT
|
||||
? downstream->get_request_http2_authority().c_str()
|
||||
: (get_config()->http2_proxy || get_config()->client_proxy)
|
||||
? construct_absolute_request_uri(downstream).c_str()
|
||||
: downstream->get_request_path().empty()
|
||||
? downstream->get_request_method() == HTTP_OPTIONS
|
||||
? "*"
|
||||
: "-"
|
||||
: downstream->get_request_path().c_str(),
|
||||
|
||||
alpn_.c_str(),
|
||||
nghttp2::ssl::get_tls_session_info(&tls_info, conn_.tls.ssl),
|
||||
@@ -856,4 +842,225 @@ ev_io *ClientHandler::get_wev() { return &conn_.wev; }
|
||||
|
||||
Worker *ClientHandler::get_worker() const { return worker_; }
|
||||
|
||||
namespace {
|
||||
ssize_t parse_proxy_line_port(const uint8_t *first, const uint8_t *last) {
|
||||
auto p = first;
|
||||
int32_t port = 0;
|
||||
|
||||
if (p == last) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (*p == '0') {
|
||||
if (p + 1 != last && util::isDigit(*(p + 1))) {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (; p != last && util::isDigit(*p); ++p) {
|
||||
port *= 10;
|
||||
port += *p - '0';
|
||||
|
||||
if (port > 65535) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return p - first;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int ClientHandler::on_proxy_protocol_finish() {
|
||||
setup_upstream_io_callback();
|
||||
|
||||
// Run on_read to process data left in buffer since they are not
|
||||
// notified further
|
||||
if (on_read() != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt
|
||||
int ClientHandler::proxy_protocol_read() {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
CLOG(INFO, this) << "PROXY-protocol: Started";
|
||||
}
|
||||
|
||||
auto first = rb_.pos;
|
||||
|
||||
// NULL character really destroys functions which expects NULL
|
||||
// terminated string. We won't expect it in PROXY protocol line, so
|
||||
// find it here.
|
||||
auto chrs = std::array<char, 2>{{'\n', '\0'}};
|
||||
|
||||
constexpr size_t MAX_PROXY_LINELEN = 107;
|
||||
|
||||
auto bufend = rb_.pos + std::min(MAX_PROXY_LINELEN, rb_.rleft());
|
||||
|
||||
auto end =
|
||||
std::find_first_of(rb_.pos, bufend, std::begin(chrs), std::end(chrs));
|
||||
|
||||
if (end == bufend || *end == '\0' || end == rb_.pos || *(end - 1) != '\r') {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
CLOG(INFO, this) << "PROXY-protocol-v1: No ending CR LF sequence found";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
--end;
|
||||
|
||||
constexpr const char HEADER[] = "PROXY ";
|
||||
|
||||
if (static_cast<size_t>(end - rb_.pos) < str_size(HEADER)) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
CLOG(INFO, this) << "PROXY-protocol-v1: PROXY version 1 ID not found";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!util::streq_l(HEADER, rb_.pos, str_size(HEADER))) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
CLOG(INFO, this) << "PROXY-protocol-v1: Bad PROXY protocol version 1 ID";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
rb_.drain(str_size(HEADER));
|
||||
|
||||
int family;
|
||||
|
||||
if (rb_.pos[0] == 'T') {
|
||||
if (end - rb_.pos < 5) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
CLOG(INFO, this) << "PROXY-protocol-v1: INET protocol family not found";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (rb_.pos[1] != 'C' || rb_.pos[2] != 'P') {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
CLOG(INFO, this) << "PROXY-protocol-v1: Unknown INET protocol family";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch (rb_.pos[3]) {
|
||||
case '4':
|
||||
family = AF_INET;
|
||||
break;
|
||||
case '6':
|
||||
family = AF_INET6;
|
||||
break;
|
||||
default:
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
CLOG(INFO, this) << "PROXY-protocol-v1: Unknown INET protocol family";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
rb_.drain(5);
|
||||
} else {
|
||||
if (end - rb_.pos < 7) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
CLOG(INFO, this) << "PROXY-protocol-v1: INET protocol family not found";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
if (!util::streq_l("UNKNOWN", rb_.pos, 7)) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
CLOG(INFO, this) << "PROXY-protocol-v1: Unknown INET protocol family";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
rb_.drain(end + 2 - rb_.pos);
|
||||
|
||||
return on_proxy_protocol_finish();
|
||||
}
|
||||
|
||||
// source address
|
||||
auto token_end = std::find(rb_.pos, end, ' ');
|
||||
if (token_end == end) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
CLOG(INFO, this) << "PROXY-protocol-v1: Source address not found";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
*token_end = '\0';
|
||||
if (!util::numeric_host(reinterpret_cast<const char *>(rb_.pos), family)) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
CLOG(INFO, this) << "PROXY-protocol-v1: Invalid source address";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto src_addr = rb_.pos;
|
||||
auto src_addrlen = token_end - rb_.pos;
|
||||
|
||||
rb_.drain(token_end - rb_.pos + 1);
|
||||
|
||||
// destination address
|
||||
token_end = std::find(rb_.pos, end, ' ');
|
||||
if (token_end == end) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
CLOG(INFO, this) << "PROXY-protocol-v1: Destination address not found";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
*token_end = '\0';
|
||||
if (!util::numeric_host(reinterpret_cast<const char *>(rb_.pos), family)) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
CLOG(INFO, this) << "PROXY-protocol-v1: Invalid destination address";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Currently we don't use destination address
|
||||
|
||||
rb_.drain(token_end - rb_.pos + 1);
|
||||
|
||||
// source port
|
||||
auto n = parse_proxy_line_port(rb_.pos, end);
|
||||
if (n <= 0 || *(rb_.pos + n) != ' ') {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
CLOG(INFO, this) << "PROXY-protocol-v1: Invalid source port";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
rb_.pos[n] = '\0';
|
||||
auto src_port = rb_.pos;
|
||||
auto src_portlen = n;
|
||||
|
||||
rb_.drain(n + 1);
|
||||
|
||||
// destination port
|
||||
n = parse_proxy_line_port(rb_.pos, end);
|
||||
if (n <= 0 || rb_.pos + n != end) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
CLOG(INFO, this) << "PROXY-protocol-v1: Invalid destination port";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Currently we don't use destination port
|
||||
|
||||
rb_.drain(end + 2 - rb_.pos);
|
||||
|
||||
ipaddr_.assign(src_addr, src_addr + src_addrlen);
|
||||
port_.assign(src_port, src_port + src_portlen);
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
CLOG(INFO, this) << "PROXY-protocol-v1: Finished, " << (rb_.pos - first)
|
||||
<< " bytes read";
|
||||
}
|
||||
|
||||
return on_proxy_protocol_finish();
|
||||
}
|
||||
|
||||
} // namespace shrpx
|
||||
|
||||
@@ -56,6 +56,7 @@ public:
|
||||
const char *port);
|
||||
~ClientHandler();
|
||||
|
||||
int noop();
|
||||
// Performs clear text I/O
|
||||
int read_clear();
|
||||
int write_clear();
|
||||
@@ -71,6 +72,9 @@ public:
|
||||
int upstream_http1_connhd_read();
|
||||
int upstream_write();
|
||||
|
||||
int proxy_protocol_read();
|
||||
int on_proxy_protocol_finish();
|
||||
|
||||
// Performs I/O operation. Internally calls on_read()/on_write().
|
||||
int do_read();
|
||||
int do_write();
|
||||
@@ -130,6 +134,8 @@ public:
|
||||
void signal_write();
|
||||
ev_io *get_wev();
|
||||
|
||||
void setup_upstream_io_callback();
|
||||
|
||||
private:
|
||||
Connection conn_;
|
||||
ev_timer reneg_shutdown_timer_;
|
||||
|
||||
@@ -625,6 +625,7 @@ void parse_mapping(const DownstreamAddr &addr, const char *src) {
|
||||
|
||||
// generated by gennghttpxfun.py
|
||||
enum {
|
||||
SHRPX_OPTID_ACCEPT_PROXY_PROTOCOL,
|
||||
SHRPX_OPTID_ACCESSLOG_FILE,
|
||||
SHRPX_OPTID_ACCESSLOG_FORMAT,
|
||||
SHRPX_OPTID_ACCESSLOG_SYSLOG,
|
||||
@@ -696,6 +697,8 @@ enum {
|
||||
SHRPX_OPTID_PRIVATE_KEY_PASSWD_FILE,
|
||||
SHRPX_OPTID_READ_BURST,
|
||||
SHRPX_OPTID_READ_RATE,
|
||||
SHRPX_OPTID_REQUEST_PHASE_FILE,
|
||||
SHRPX_OPTID_RESPONSE_PHASE_FILE,
|
||||
SHRPX_OPTID_RLIMIT_NOFILE,
|
||||
SHRPX_OPTID_STREAM_READ_TIMEOUT,
|
||||
SHRPX_OPTID_STREAM_WRITE_TIMEOUT,
|
||||
@@ -1017,6 +1020,11 @@ int option_lookup_token(const char *name, size_t namelen) {
|
||||
break;
|
||||
case 18:
|
||||
switch (name[17]) {
|
||||
case 'e':
|
||||
if (util::strieq_l("request-phase-fil", name, 17)) {
|
||||
return SHRPX_OPTID_REQUEST_PHASE_FILE;
|
||||
}
|
||||
break;
|
||||
case 'r':
|
||||
if (util::strieq_l("add-request-heade", name, 17)) {
|
||||
return SHRPX_OPTID_ADD_REQUEST_HEADER;
|
||||
@@ -1035,6 +1043,9 @@ int option_lookup_token(const char *name, size_t namelen) {
|
||||
if (util::strieq_l("no-location-rewrit", name, 18)) {
|
||||
return SHRPX_OPTID_NO_LOCATION_REWRITE;
|
||||
}
|
||||
if (util::strieq_l("response-phase-fil", name, 18)) {
|
||||
return SHRPX_OPTID_RESPONSE_PHASE_FILE;
|
||||
}
|
||||
if (util::strieq_l("tls-ticket-key-fil", name, 18)) {
|
||||
return SHRPX_OPTID_TLS_TICKET_KEY_FILE;
|
||||
}
|
||||
@@ -1089,6 +1100,11 @@ int option_lookup_token(const char *name, size_t namelen) {
|
||||
return SHRPX_OPTID_BACKEND_TLS_SNI_FIELD;
|
||||
}
|
||||
break;
|
||||
case 'l':
|
||||
if (util::strieq_l("accept-proxy-protoco", name, 20)) {
|
||||
return SHRPX_OPTID_ACCEPT_PROXY_PROTOCOL;
|
||||
}
|
||||
break;
|
||||
case 'r':
|
||||
if (util::strieq_l("tls-ticket-key-ciphe", name, 20)) {
|
||||
return SHRPX_OPTID_TLS_TICKET_KEY_CIPHER;
|
||||
@@ -1363,7 +1379,12 @@ int parse_config(const char *opt, const char *optarg,
|
||||
return 0;
|
||||
}
|
||||
case SHRPX_OPTID_WORKERS:
|
||||
#ifdef NOTHREADS
|
||||
LOG(WARN) << "Threading disabled at build time, no threads created.";
|
||||
return 0;
|
||||
#else // !NOTHREADS
|
||||
return parse_uint(&mod_config()->num_worker, opt, optarg);
|
||||
#endif // !NOTHREADS
|
||||
case SHRPX_OPTID_HTTP2_MAX_CONCURRENT_STREAMS:
|
||||
return parse_uint(&mod_config()->http2_max_concurrent_streams, opt, optarg);
|
||||
case SHRPX_OPTID_LOG_LEVEL:
|
||||
@@ -1938,6 +1959,26 @@ int parse_config(const char *opt, const char *optarg,
|
||||
case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL:
|
||||
return parse_uint(&mod_config()->tls_ticket_key_memcached_max_fail, opt,
|
||||
optarg);
|
||||
case SHRPX_OPTID_REQUEST_PHASE_FILE:
|
||||
#ifdef HAVE_MRUBY
|
||||
mod_config()->request_phase_file = strcopy(optarg);
|
||||
#else // !HAVE_MRUBY
|
||||
LOG(WARN) << opt
|
||||
<< ": ignored because mruby support is disabled at build time.";
|
||||
#endif // !HAVE_MRUBY
|
||||
return 0;
|
||||
case SHRPX_OPTID_RESPONSE_PHASE_FILE:
|
||||
#ifdef HAVE_MRUBY
|
||||
mod_config()->response_phase_file = strcopy(optarg);
|
||||
#else // !HAVE_MRUBY
|
||||
LOG(WARN) << opt
|
||||
<< ": ignored because mruby support is disabled at build time.";
|
||||
#endif // !HAVE_MRUBY
|
||||
return 0;
|
||||
case SHRPX_OPTID_ACCEPT_PROXY_PROTOCOL:
|
||||
mod_config()->accept_proxy_protocol = util::strieq(optarg, "yes");
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_CONF:
|
||||
LOG(WARN) << "conf: ignored";
|
||||
|
||||
|
||||
@@ -183,6 +183,9 @@ constexpr char SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY[] =
|
||||
"tls-ticket-key-memcached-max-retry";
|
||||
constexpr char SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL[] =
|
||||
"tls-ticket-key-memcached-max-fail";
|
||||
constexpr char SHRPX_OPT_REQUEST_PHASE_FILE[] = "request-phase-file";
|
||||
constexpr char SHRPX_OPT_RESPONSE_PHASE_FILE[] = "response-phase-file";
|
||||
constexpr char SHRPX_OPT_ACCEPT_PROXY_PROTOCOL[] = "accept-proxy-protocol";
|
||||
|
||||
union sockaddr_union {
|
||||
sockaddr_storage storage;
|
||||
@@ -314,6 +317,8 @@ struct Config {
|
||||
std::unique_ptr<char[]> user;
|
||||
std::unique_ptr<char[]> session_cache_memcached_host;
|
||||
std::unique_ptr<char[]> tls_ticket_key_memcached_host;
|
||||
std::unique_ptr<char[]> request_phase_file;
|
||||
std::unique_ptr<char[]> response_phase_file;
|
||||
FILE *http2_upstream_dump_request_header;
|
||||
FILE *http2_upstream_dump_response_header;
|
||||
nghttp2_session_callbacks *http2_upstream_callbacks;
|
||||
@@ -405,6 +410,7 @@ struct Config {
|
||||
bool no_ocsp;
|
||||
// true if --tls-ticket-key-cipher is used
|
||||
bool tls_ticket_key_cipher_given;
|
||||
bool accept_proxy_protocol;
|
||||
};
|
||||
|
||||
const Config *get_config();
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "shrpx_ssl.h"
|
||||
#include "shrpx_memcached_request.h"
|
||||
#include "memchunk.h"
|
||||
#include "util.h"
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
@@ -145,20 +146,7 @@ int shrpx_bio_write(BIO *b, const char *buf, int len) {
|
||||
if (conn->tls.initial_handshake_done) {
|
||||
// After handshake finished, send |buf| of length |len| to the
|
||||
// socket directly.
|
||||
if (wbuf.rleft()) {
|
||||
std::array<struct iovec, 4> iov;
|
||||
auto iovcnt = wbuf.riovec(iov.data(), iov.size());
|
||||
auto nwrite = conn->writev_clear(iov.data(), iovcnt);
|
||||
if (nwrite < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
wbuf.drain(nwrite);
|
||||
if (wbuf.rleft()) {
|
||||
BIO_set_retry_write(b);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
assert(wbuf.rleft() == 0);
|
||||
auto nwrite = conn->write_clear(buf, len);
|
||||
if (nwrite < 0) {
|
||||
return -1;
|
||||
@@ -300,6 +288,10 @@ int Connection::tls_handshake() {
|
||||
}
|
||||
}
|
||||
|
||||
if (tls.initial_handshake_done) {
|
||||
return write_tls_pending_handshake();
|
||||
}
|
||||
|
||||
switch (tls.handshake_state) {
|
||||
case TLS_CONN_WAIT_FOR_SESSION_CACHE:
|
||||
return SHRPX_ERR_INPROGRESS;
|
||||
@@ -365,7 +357,10 @@ int Connection::tls_handshake() {
|
||||
return SHRPX_ERR_INPROGRESS;
|
||||
}
|
||||
|
||||
if (tls.wbuf.rleft()) {
|
||||
// Don't send handshake data if handshake was completed in OpenSSL
|
||||
// routine. We have to check HTTP/2 requirement if HTTP/2 was
|
||||
// negotiated before sending finished message to the peer.
|
||||
if (rv != 1 && tls.wbuf.rleft()) {
|
||||
// First write indicates that resumption stuff has done.
|
||||
if (tls.handshake_state != TLS_CONN_WRITE_STARTED) {
|
||||
tls.handshake_state = TLS_CONN_WRITE_STARTED;
|
||||
@@ -401,8 +396,42 @@ int Connection::tls_handshake() {
|
||||
return SHRPX_ERR_INPROGRESS;
|
||||
}
|
||||
|
||||
// Handshake was done
|
||||
|
||||
rv = check_http2_requirement();
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Just in case
|
||||
tls.rbuf.disable_peek(true);
|
||||
|
||||
tls.initial_handshake_done = true;
|
||||
|
||||
return write_tls_pending_handshake();
|
||||
}
|
||||
|
||||
int Connection::write_tls_pending_handshake() {
|
||||
// Send handshake data left in the buffer
|
||||
while (tls.wbuf.rleft()) {
|
||||
std::array<struct iovec, 4> iov;
|
||||
auto iovcnt = tls.wbuf.riovec(iov.data(), iov.size());
|
||||
auto nwrite = writev_clear(iov.data(), iovcnt);
|
||||
if (nwrite < 0) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
LOG(INFO) << "tls: handshake write error";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
if (nwrite == 0) {
|
||||
wlimit.startw();
|
||||
ev_timer_again(loop, &wt);
|
||||
|
||||
return SHRPX_ERR_INPROGRESS;
|
||||
}
|
||||
tls.wbuf.drain(nwrite);
|
||||
}
|
||||
|
||||
// We have to start read watcher, since later stage of code expects
|
||||
// this.
|
||||
rlimit.startw();
|
||||
@@ -422,6 +451,31 @@ int Connection::tls_handshake() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Connection::check_http2_requirement() {
|
||||
const unsigned char *next_proto = nullptr;
|
||||
unsigned int next_proto_len;
|
||||
|
||||
SSL_get0_next_proto_negotiated(tls.ssl, &next_proto, &next_proto_len);
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||
if (next_proto == nullptr) {
|
||||
SSL_get0_alpn_selected(tls.ssl, &next_proto, &next_proto_len);
|
||||
}
|
||||
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||
if (next_proto == nullptr ||
|
||||
!util::check_h2_is_selected(next_proto, next_proto_len)) {
|
||||
return 0;
|
||||
}
|
||||
if (!nghttp2::ssl::check_http2_requirement(tls.ssl)) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
LOG(INFO) << "TLSv1.2 and/or black listed cipher suite was negotiated. "
|
||||
"HTTP/2 must not be used.";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
namespace {
|
||||
const size_t SHRPX_SMALL_WRITE_LIMIT = 1300;
|
||||
const size_t SHRPX_WARMUP_THRESHOLD = 1 << 20;
|
||||
|
||||
@@ -84,6 +84,9 @@ struct Connection {
|
||||
void prepare_server_handshake();
|
||||
|
||||
int tls_handshake();
|
||||
int write_tls_pending_handshake();
|
||||
|
||||
int check_http2_requirement();
|
||||
|
||||
// All write_* and writev_clear functions return number of bytes
|
||||
// written. If nothing cannot be written (e.g., there is no
|
||||
|
||||
@@ -130,6 +130,13 @@ ConnectionHandler::~ConnectionHandler() {
|
||||
}
|
||||
SSL_CTX_free(ssl_ctx);
|
||||
}
|
||||
|
||||
// Free workers before destroying ev_loop
|
||||
workers_.clear();
|
||||
|
||||
for (auto loop : worker_loops_) {
|
||||
ev_loop_destroy(loop);
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionHandler::set_ticket_keys_to_worker(
|
||||
@@ -149,7 +156,7 @@ void ConnectionHandler::worker_reopen_log_files() {
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionHandler::create_single_worker() {
|
||||
int ConnectionHandler::create_single_worker() {
|
||||
auto cert_tree = ssl::create_cert_lookup_tree();
|
||||
auto sv_ssl_ctx = ssl::setup_server_ssl_context(all_ssl_ctx_, cert_tree);
|
||||
auto cl_ssl_ctx = ssl::setup_client_ssl_context();
|
||||
@@ -160,9 +167,16 @@ void ConnectionHandler::create_single_worker() {
|
||||
|
||||
single_worker_ = make_unique<Worker>(loop_, sv_ssl_ctx, cl_ssl_ctx, cert_tree,
|
||||
ticket_keys_);
|
||||
#ifdef HAVE_MRUBY
|
||||
if (single_worker_->create_mruby_context() != 0) {
|
||||
return -1;
|
||||
}
|
||||
#endif // HAVE_MRUBY
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ConnectionHandler::create_worker_thread(size_t num) {
|
||||
int ConnectionHandler::create_worker_thread(size_t num) {
|
||||
#ifndef NOTHREADS
|
||||
assert(workers_.size() == 0);
|
||||
|
||||
@@ -179,14 +193,26 @@ void ConnectionHandler::create_worker_thread(size_t num) {
|
||||
|
||||
auto worker = make_unique<Worker>(loop, sv_ssl_ctx, cl_ssl_ctx, cert_tree,
|
||||
ticket_keys_);
|
||||
worker->run_async();
|
||||
#ifdef HAVE_MRUBY
|
||||
if (worker->create_mruby_context() != 0) {
|
||||
return -1;
|
||||
}
|
||||
#endif // HAVE_MRUBY
|
||||
|
||||
workers_.push_back(std::move(worker));
|
||||
worker_loops_.push_back(loop);
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
LLOG(INFO, this) << "Created thread #" << workers_.size() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &worker : workers_) {
|
||||
worker->run_async();
|
||||
}
|
||||
#endif // NOTHREADS
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ConnectionHandler::join_worker() {
|
||||
|
||||
@@ -73,10 +73,10 @@ public:
|
||||
~ConnectionHandler();
|
||||
int handle_connection(int fd, sockaddr *addr, int addrlen);
|
||||
// Creates Worker object for single threaded configuration.
|
||||
void create_single_worker();
|
||||
int create_single_worker();
|
||||
// Creates |num| Worker objects for multi threaded configuration.
|
||||
// The |num| must be strictly more than 1.
|
||||
void create_worker_thread(size_t num);
|
||||
int create_worker_thread(size_t num);
|
||||
void
|
||||
set_ticket_keys_to_worker(const std::shared_ptr<TicketKeys> &ticket_keys);
|
||||
void worker_reopen_log_files();
|
||||
@@ -127,6 +127,8 @@ private:
|
||||
// Stores all SSL_CTX objects.
|
||||
std::vector<SSL_CTX *> all_ssl_ctx_;
|
||||
OCSPUpdateContext ocsp_;
|
||||
// ev_loop for each worker
|
||||
std::vector<struct ev_loop *> worker_loops_;
|
||||
// Worker instances when multi threaded mode (-nN, N >= 2) is used.
|
||||
std::vector<std::unique_ptr<Worker>> workers_;
|
||||
// Worker instance used when single threaded mode (-n1) is used.
|
||||
|
||||
@@ -34,6 +34,11 @@
|
||||
#include "shrpx_error.h"
|
||||
#include "shrpx_downstream_connection.h"
|
||||
#include "shrpx_downstream_queue.h"
|
||||
#include "shrpx_worker.h"
|
||||
#include "shrpx_http2_session.h"
|
||||
#ifdef HAVE_MRUBY
|
||||
#include "shrpx_mruby.h"
|
||||
#endif // HAVE_MRUBY
|
||||
#include "util.h"
|
||||
#include "http2.h"
|
||||
|
||||
@@ -160,6 +165,14 @@ Downstream::~Downstream() {
|
||||
ev_timer_stop(loop, &upstream_wtimer_);
|
||||
ev_timer_stop(loop, &downstream_rtimer_);
|
||||
ev_timer_stop(loop, &downstream_wtimer_);
|
||||
|
||||
#ifdef HAVE_MRUBY
|
||||
auto handler = upstream_->get_client_handler();
|
||||
auto worker = handler->get_worker();
|
||||
auto mruby_ctx = worker->get_mruby_context();
|
||||
|
||||
mruby_ctx->delete_downstream(this);
|
||||
#endif // HAVE_MRUBY
|
||||
}
|
||||
|
||||
// DownstreamConnection may refer to this object. Delete it now
|
||||
@@ -240,6 +253,8 @@ const Headers &Downstream::get_request_headers() const {
|
||||
return request_headers_;
|
||||
}
|
||||
|
||||
Headers &Downstream::get_request_headers() { return request_headers_; }
|
||||
|
||||
void Downstream::assemble_request_cookie() {
|
||||
std::string &cookie = assembled_request_cookie_;
|
||||
cookie = "";
|
||||
@@ -336,6 +351,9 @@ void set_last_header_value(bool &key_prev, size_t &sum, Headers &headers,
|
||||
namespace {
|
||||
int index_headers(http2::HeaderIndex &hdidx, Headers &headers,
|
||||
int64_t &content_length) {
|
||||
http2::init_hdidx(hdidx);
|
||||
content_length = -1;
|
||||
|
||||
for (size_t i = 0; i < headers.size(); ++i) {
|
||||
auto &kv = headers[i];
|
||||
util::inp_strlower(kv.name);
|
||||
@@ -510,6 +528,10 @@ void Downstream::set_request_http2_authority(std::string authority) {
|
||||
request_http2_authority_ = std::move(authority);
|
||||
}
|
||||
|
||||
void Downstream::append_request_http2_authority(const char *data, size_t len) {
|
||||
request_http2_authority_.append(data, len);
|
||||
}
|
||||
|
||||
void Downstream::set_request_major(int major) { request_major_ = major; }
|
||||
|
||||
void Downstream::set_request_minor(int minor) { request_minor_ = minor; }
|
||||
@@ -604,6 +626,8 @@ const Headers &Downstream::get_response_headers() const {
|
||||
return response_headers_;
|
||||
}
|
||||
|
||||
Headers &Downstream::get_response_headers() { return response_headers_; }
|
||||
|
||||
int Downstream::index_response_headers() {
|
||||
return index_headers(response_hdidx_, response_headers_,
|
||||
response_content_length_);
|
||||
@@ -614,6 +638,10 @@ Downstream::get_response_header(int16_t token) const {
|
||||
return http2::get_header(response_hdidx_, token, response_headers_);
|
||||
}
|
||||
|
||||
Headers::value_type *Downstream::get_response_header(int16_t token) {
|
||||
return http2::get_header(response_hdidx_, token, response_headers_);
|
||||
}
|
||||
|
||||
void Downstream::rewrite_location_response_header(
|
||||
const std::string &upstream_scheme) {
|
||||
auto hd =
|
||||
|
||||
@@ -96,6 +96,7 @@ public:
|
||||
const std::string &get_http2_settings() const;
|
||||
// downstream request API
|
||||
const Headers &get_request_headers() const;
|
||||
Headers &get_request_headers();
|
||||
// Crumbles (split cookie by ";") in request_headers_ and returns
|
||||
// them. Headers::no_index is inherited.
|
||||
Headers crumble_request_cookie();
|
||||
@@ -149,15 +150,19 @@ public:
|
||||
get_request_start_time() const;
|
||||
void append_request_path(const char *data, size_t len);
|
||||
// Returns request path. For HTTP/1.1, this is request-target. For
|
||||
// HTTP/2, this is :path header field value.
|
||||
// HTTP/2, this is :path header field value. For CONNECT request,
|
||||
// this is empty.
|
||||
const std::string &get_request_path() const;
|
||||
// Returns HTTP/2 :scheme header field value.
|
||||
const std::string &get_request_http2_scheme() const;
|
||||
void set_request_http2_scheme(std::string scheme);
|
||||
// Returns HTTP/2 :authority header field value. We also set the
|
||||
// value retrieved from absolute-form HTTP/1 request.
|
||||
// Returns :authority or host header field value. We may deduce it
|
||||
// from absolute-form HTTP/1 request. We also store authority-form
|
||||
// HTTP/1 request. This could be empty if request comes from
|
||||
// HTTP/1.0 without Host header field and origin-form.
|
||||
const std::string &get_request_http2_authority() const;
|
||||
void set_request_http2_authority(std::string authority);
|
||||
void append_request_http2_authority(const char *data, size_t len);
|
||||
void set_request_major(int major);
|
||||
void set_request_minor(int minor);
|
||||
int get_request_major() const;
|
||||
@@ -207,6 +212,7 @@ public:
|
||||
bool request_submission_ready() const;
|
||||
// downstream response API
|
||||
const Headers &get_response_headers() const;
|
||||
Headers &get_response_headers();
|
||||
// Lower the response header field names and indexes response
|
||||
// headers. If there are invalid headers (e.g., multiple
|
||||
// Content-Length with different values), returns -1.
|
||||
@@ -216,6 +222,7 @@ public:
|
||||
// the beginning. If no such header is found, returns nullptr.
|
||||
// This function must be called after response headers are indexed.
|
||||
const Headers::value_type *get_response_header(int16_t token) const;
|
||||
Headers::value_type *get_response_header(int16_t token);
|
||||
// Rewrites the location response header field.
|
||||
void rewrite_location_response_header(const std::string &upstream_scheme);
|
||||
void add_response_header(std::string name, std::string value);
|
||||
|
||||
@@ -36,6 +36,7 @@ enum ErrorCode {
|
||||
SHRPX_ERR_NETWORK = -100,
|
||||
SHRPX_ERR_EOF = -101,
|
||||
SHRPX_ERR_INPROGRESS = -102,
|
||||
SHRPX_ERR_DCONN_CANCELED = -103,
|
||||
};
|
||||
|
||||
} // namespace shrpx
|
||||
|
||||
@@ -251,10 +251,10 @@ int Http2DownstreamConnection::push_request_headers() {
|
||||
|
||||
downstream_->set_request_pending(false);
|
||||
|
||||
auto method = downstream_->get_request_method();
|
||||
auto no_host_rewrite = get_config()->no_host_rewrite ||
|
||||
get_config()->http2_proxy ||
|
||||
get_config()->client_proxy ||
|
||||
downstream_->get_request_method() == HTTP_CONNECT;
|
||||
get_config()->client_proxy || method == HTTP_CONNECT;
|
||||
|
||||
// http2session_ has already in CONNECTED state, so we can get
|
||||
// addr_idx here.
|
||||
@@ -265,37 +265,21 @@ int Http2DownstreamConnection::push_request_headers() {
|
||||
.addrs[addr_idx]
|
||||
.hostport.get();
|
||||
|
||||
const char *authority = nullptr, *host = nullptr;
|
||||
if (!no_host_rewrite) {
|
||||
if (!downstream_->get_request_http2_authority().empty()) {
|
||||
authority = downstream_hostport;
|
||||
}
|
||||
if (downstream_->get_request_header(http2::HD_HOST)) {
|
||||
host = downstream_hostport;
|
||||
}
|
||||
} else {
|
||||
if (!downstream_->get_request_http2_authority().empty()) {
|
||||
authority = downstream_->get_request_http2_authority().c_str();
|
||||
}
|
||||
auto h = downstream_->get_request_header(http2::HD_HOST);
|
||||
if (h) {
|
||||
host = h->value.c_str();
|
||||
}
|
||||
// For HTTP/1.0 request, there is no authority in request. In that
|
||||
// case, we use backend server's host nonetheless.
|
||||
const char *authority = downstream_hostport;
|
||||
auto &req_authority = downstream_->get_request_http2_authority();
|
||||
if (no_host_rewrite && !req_authority.empty()) {
|
||||
authority = req_authority.c_str();
|
||||
}
|
||||
|
||||
if (!authority && !host) {
|
||||
// upstream is HTTP/1.0. We use backend server's host
|
||||
// nonetheless.
|
||||
host = downstream_hostport;
|
||||
if (!authority) {
|
||||
authority = downstream_hostport;
|
||||
}
|
||||
|
||||
if (authority) {
|
||||
downstream_->set_request_downstream_host(authority);
|
||||
} else {
|
||||
downstream_->set_request_downstream_host(host);
|
||||
}
|
||||
downstream_->set_request_downstream_host(authority);
|
||||
|
||||
size_t nheader = downstream_->get_request_headers().size();
|
||||
auto nheader = downstream_->get_request_headers().size();
|
||||
|
||||
Headers cookies;
|
||||
if (!get_config()->http2_no_cookie_crumbling) {
|
||||
@@ -306,7 +290,7 @@ int Http2DownstreamConnection::push_request_headers() {
|
||||
// 1. :method
|
||||
// 2. :scheme
|
||||
// 3. :path
|
||||
// 4. :authority or host (at least either of them exists)
|
||||
// 4. :authority
|
||||
// 5. via (optional)
|
||||
// 6. x-forwarded-for (optional)
|
||||
// 7. x-forwarded-proto (optional)
|
||||
@@ -315,33 +299,23 @@ int Http2DownstreamConnection::push_request_headers() {
|
||||
nva.reserve(nheader + 8 + cookies.size() +
|
||||
get_config()->add_request_headers.size());
|
||||
|
||||
nva.push_back(http2::make_nv_lc(
|
||||
":method", http2::to_method_string(downstream_->get_request_method())));
|
||||
nva.push_back(http2::make_nv_lc(":method", http2::to_method_string(method)));
|
||||
|
||||
auto &scheme = downstream_->get_request_http2_scheme();
|
||||
|
||||
if (downstream_->get_request_method() == HTTP_CONNECT) {
|
||||
if (authority) {
|
||||
nva.push_back(http2::make_nv_lc(":authority", authority));
|
||||
} else {
|
||||
nva.push_back(
|
||||
http2::make_nv_ls(":authority", downstream_->get_request_path()));
|
||||
}
|
||||
} else {
|
||||
nva.push_back(http2::make_nv_lc(":authority", authority));
|
||||
|
||||
if (method != HTTP_CONNECT) {
|
||||
assert(!scheme.empty());
|
||||
|
||||
nva.push_back(http2::make_nv_ls(":scheme", scheme));
|
||||
|
||||
if (authority) {
|
||||
nva.push_back(http2::make_nv_lc(":authority", authority));
|
||||
auto &path = downstream_->get_request_path();
|
||||
if (method == HTTP_OPTIONS && path.empty()) {
|
||||
nva.push_back(http2::make_nv_ll(":path", "*"));
|
||||
} else {
|
||||
nva.push_back(http2::make_nv_ls(":path", path));
|
||||
}
|
||||
|
||||
nva.push_back(http2::make_nv_ls(":path", downstream_->get_request_path()));
|
||||
}
|
||||
|
||||
// only emit host header field if :authority is not emitted. They
|
||||
// both must be the same value.
|
||||
if (!authority && host) {
|
||||
nva.push_back(http2::make_nv_lc("host", host));
|
||||
}
|
||||
|
||||
http2::copy_headers_to_nva(nva, downstream_->get_request_headers());
|
||||
|
||||
@@ -903,9 +903,15 @@ int on_response_headers(Http2Session *http2session, Downstream *downstream,
|
||||
|
||||
rv = upstream->on_downstream_header_complete(downstream);
|
||||
if (rv != 0) {
|
||||
http2session->submit_rst_stream(frame->hd.stream_id,
|
||||
NGHTTP2_PROTOCOL_ERROR);
|
||||
downstream->set_response_state(Downstream::MSG_RESET);
|
||||
// Handling early return (in other words, response was hijacked by
|
||||
// mruby scripting).
|
||||
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
|
||||
http2session->submit_rst_stream(frame->hd.stream_id, NGHTTP2_CANCEL);
|
||||
} else {
|
||||
http2session->submit_rst_stream(frame->hd.stream_id,
|
||||
NGHTTP2_INTERNAL_ERROR);
|
||||
downstream->set_response_state(Downstream::MSG_RESET);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -37,6 +37,9 @@
|
||||
#include "shrpx_http.h"
|
||||
#include "shrpx_worker.h"
|
||||
#include "shrpx_http2_session.h"
|
||||
#ifdef HAVE_MRUBY
|
||||
#include "shrpx_mruby.h"
|
||||
#endif // HAVE_MRUBY
|
||||
#include "http2.h"
|
||||
#include "util.h"
|
||||
#include "base64.h"
|
||||
@@ -291,9 +294,16 @@ int Http2Upstream::on_request_headers(Downstream *downstream,
|
||||
|
||||
downstream->set_request_method(method_token);
|
||||
downstream->set_request_http2_scheme(http2::value_to_str(scheme));
|
||||
// nghttp2 library guarantees either :authority or host exist
|
||||
if (!authority) {
|
||||
authority = downstream->get_request_header(http2::HD_HOST);
|
||||
}
|
||||
downstream->set_request_http2_authority(http2::value_to_str(authority));
|
||||
|
||||
if (path) {
|
||||
if (get_config()->http2_proxy || get_config()->client_proxy) {
|
||||
if (method_token == HTTP_OPTIONS && path->value == "*") {
|
||||
// Server-wide OPTIONS request. Path is empty.
|
||||
} else if (get_config()->http2_proxy || get_config()->client_proxy) {
|
||||
downstream->set_request_path(http2::value_to_str(path));
|
||||
} else {
|
||||
auto &value = path->value;
|
||||
@@ -309,12 +319,31 @@ int Http2Upstream::on_request_headers(Downstream *downstream,
|
||||
downstream->inspect_http2_request();
|
||||
|
||||
downstream->set_request_state(Downstream::HEADER_COMPLETE);
|
||||
|
||||
#ifdef HAVE_MRUBY
|
||||
auto upstream = downstream->get_upstream();
|
||||
auto handler = upstream->get_client_handler();
|
||||
auto worker = handler->get_worker();
|
||||
auto mruby_ctx = worker->get_mruby_context();
|
||||
|
||||
if (mruby_ctx->run_on_request_proc(downstream) != 0) {
|
||||
if (error_reply(downstream, 500) != 0) {
|
||||
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif // HAVE_MRUBY
|
||||
|
||||
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
||||
downstream->disable_upstream_rtimer();
|
||||
|
||||
downstream->set_request_state(Downstream::MSG_COMPLETE);
|
||||
}
|
||||
|
||||
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
start_downstream(downstream);
|
||||
|
||||
return 0;
|
||||
@@ -558,6 +587,20 @@ int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
||||
// downstream is in pending queue.
|
||||
auto ptr = downstream.get();
|
||||
upstream->add_pending_downstream(std::move(downstream));
|
||||
|
||||
#ifdef HAVE_MRUBY
|
||||
auto worker = handler->get_worker();
|
||||
auto mruby_ctx = worker->get_mruby_context();
|
||||
|
||||
if (mruby_ctx->run_on_request_proc(ptr) != 0) {
|
||||
if (upstream->error_reply(ptr, 500) != 0) {
|
||||
upstream->rst_stream(ptr, NGHTTP2_INTERNAL_ERROR);
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif // HAVE_MRUBY
|
||||
|
||||
upstream->start_downstream(ptr);
|
||||
|
||||
return 0;
|
||||
@@ -898,6 +941,11 @@ int Http2Upstream::downstream_read(DownstreamConnection *dconn) {
|
||||
if (rv == SHRPX_ERR_EOF) {
|
||||
return downstream_eof(dconn);
|
||||
}
|
||||
if (rv == SHRPX_ERR_DCONN_CANCELED) {
|
||||
downstream->pop_downstream_connection();
|
||||
handler_->signal_write();
|
||||
return 0;
|
||||
}
|
||||
if (rv != 0) {
|
||||
if (rv != SHRPX_ERR_NETWORK) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
@@ -1120,6 +1168,63 @@ ssize_t downstream_data_read_callback(nghttp2_session *session,
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int Http2Upstream::send_reply(Downstream *downstream, const uint8_t *body,
|
||||
size_t bodylen) {
|
||||
int rv;
|
||||
|
||||
nghttp2_data_provider data_prd, *data_prd_ptr = nullptr;
|
||||
|
||||
if (bodylen) {
|
||||
data_prd.source.ptr = downstream;
|
||||
data_prd.read_callback = downstream_data_read_callback;
|
||||
data_prd_ptr = &data_prd;
|
||||
}
|
||||
|
||||
auto status_code_str = util::utos(downstream->get_response_http_status());
|
||||
auto &headers = downstream->get_response_headers();
|
||||
auto nva = std::vector<nghttp2_nv>();
|
||||
// 2 for :status and server
|
||||
nva.reserve(2 + headers.size());
|
||||
|
||||
nva.push_back(http2::make_nv_ls(":status", status_code_str));
|
||||
|
||||
for (auto &kv : headers) {
|
||||
if (kv.name.empty() || kv.name[0] == ':') {
|
||||
continue;
|
||||
}
|
||||
switch (kv.token) {
|
||||
case http2::HD_CONNECTION:
|
||||
case http2::HD_KEEP_ALIVE:
|
||||
case http2::HD_PROXY_CONNECTION:
|
||||
case http2::HD_TE:
|
||||
case http2::HD_TRANSFER_ENCODING:
|
||||
case http2::HD_UPGRADE:
|
||||
continue;
|
||||
}
|
||||
nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index));
|
||||
}
|
||||
|
||||
if (!downstream->get_response_header(http2::HD_SERVER)) {
|
||||
nva.push_back(http2::make_nv_lc("server", get_config()->server_name));
|
||||
}
|
||||
|
||||
rv = nghttp2_submit_response(session_, downstream->get_stream_id(),
|
||||
nva.data(), nva.size(), data_prd_ptr);
|
||||
if (nghttp2_is_fatal(rv)) {
|
||||
ULOG(FATAL, this) << "nghttp2_submit_response() failed: "
|
||||
<< nghttp2_strerror(rv);
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto buf = downstream->get_response_buf();
|
||||
|
||||
buf->append(body, bodylen);
|
||||
|
||||
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Http2Upstream::error_reply(Downstream *downstream,
|
||||
unsigned int status_code) {
|
||||
int rv;
|
||||
@@ -1133,13 +1238,17 @@ int Http2Upstream::error_reply(Downstream *downstream,
|
||||
data_prd.source.ptr = downstream;
|
||||
data_prd.read_callback = downstream_data_read_callback;
|
||||
|
||||
auto lgconf = log_config();
|
||||
lgconf->update_tstamp(std::chrono::system_clock::now());
|
||||
|
||||
auto content_length = util::utos(html.size());
|
||||
auto status_code_str = util::utos(status_code);
|
||||
auto nva =
|
||||
make_array(http2::make_nv_ls(":status", status_code_str),
|
||||
http2::make_nv_ll("content-type", "text/html; charset=UTF-8"),
|
||||
http2::make_nv_lc("server", get_config()->server_name),
|
||||
http2::make_nv_ls("content-length", content_length));
|
||||
http2::make_nv_ls("content-length", content_length),
|
||||
http2::make_nv_ls("date", lgconf->time_http_str));
|
||||
|
||||
rv = nghttp2_submit_response(session_, downstream->get_stream_id(),
|
||||
nva.data(), nva.size(), &data_prd);
|
||||
@@ -1191,6 +1300,25 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
|
||||
downstream->get_request_http2_scheme());
|
||||
}
|
||||
|
||||
#ifdef HAVE_MRUBY
|
||||
if (!downstream->get_non_final_response()) {
|
||||
auto worker = handler_->get_worker();
|
||||
auto mruby_ctx = worker->get_mruby_context();
|
||||
|
||||
if (mruby_ctx->run_on_response_proc(downstream) != 0) {
|
||||
if (error_reply(downstream, 500) != 0) {
|
||||
return -1;
|
||||
}
|
||||
// Returning -1 will signal deletion of dconn.
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
#endif // HAVE_MRUBY
|
||||
|
||||
size_t nheader = downstream->get_response_headers().size();
|
||||
auto nva = std::vector<nghttp2_nv>();
|
||||
// 3 means :status and possible server and via header field.
|
||||
@@ -1272,12 +1400,9 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
|
||||
// We need some conditions that must be fulfilled to initiate server
|
||||
// push.
|
||||
//
|
||||
// * Server push is disabled for http2 proxy, since incoming headers
|
||||
// are mixed origins. We don't know how to reliably determine the
|
||||
// authority yet.
|
||||
//
|
||||
// * If downstream is http/2, it is likely that PUSH_PROMISE is
|
||||
// coming from there, so we don't initiate PUSH_RPOMISE here.
|
||||
// * Server push is disabled for http2 proxy or client proxy, since
|
||||
// incoming headers are mixed origins. We don't know how to
|
||||
// reliably determine the authority yet.
|
||||
//
|
||||
// * We need 200 response code for associated resource. This is too
|
||||
// restrictive, we will review this later.
|
||||
@@ -1286,8 +1411,10 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
|
||||
// don't want to push for HEAD request. Not sure other methods
|
||||
// are also eligible for push.
|
||||
if (!get_config()->no_server_push &&
|
||||
get_config()->downstream_proto == PROTO_HTTP &&
|
||||
!get_config()->http2_proxy && (downstream->get_stream_id() % 2) &&
|
||||
nghttp2_session_get_remote_settings(session_,
|
||||
NGHTTP2_SETTINGS_ENABLE_PUSH) == 1 &&
|
||||
!get_config()->http2_proxy && !get_config()->client_proxy &&
|
||||
(downstream->get_stream_id() % 2) &&
|
||||
downstream->get_response_header(http2::HD_LINK) &&
|
||||
downstream->get_response_http_status() == 200 &&
|
||||
(downstream->get_request_method() == HTTP_GET ||
|
||||
@@ -1471,74 +1598,42 @@ int Http2Upstream::on_downstream_reset(bool no_retry) {
|
||||
|
||||
int Http2Upstream::prepare_push_promise(Downstream *downstream) {
|
||||
int rv;
|
||||
http_parser_url u{};
|
||||
rv = http_parser_parse_url(downstream->get_request_path().c_str(),
|
||||
downstream->get_request_path().size(), 0, &u);
|
||||
const char *base;
|
||||
size_t baselen;
|
||||
|
||||
rv = http2::get_pure_path_component(&base, &baselen,
|
||||
downstream->get_request_path());
|
||||
if (rv != 0) {
|
||||
return 0;
|
||||
}
|
||||
const char *base;
|
||||
size_t baselen;
|
||||
if (u.field_set & (1 << UF_PATH)) {
|
||||
auto &f = u.field_data[UF_PATH];
|
||||
base = downstream->get_request_path().c_str() + f.off;
|
||||
baselen = f.len;
|
||||
} else {
|
||||
base = "/";
|
||||
baselen = 1;
|
||||
}
|
||||
|
||||
for (auto &kv : downstream->get_response_headers()) {
|
||||
if (kv.token != http2::HD_LINK) {
|
||||
continue;
|
||||
}
|
||||
for (auto &link :
|
||||
http2::parse_link_header(kv.value.c_str(), kv.value.size())) {
|
||||
auto link_url = link.uri.first;
|
||||
auto link_urllen = link.uri.second - link.uri.first;
|
||||
|
||||
const char *rel;
|
||||
size_t rellen;
|
||||
const char *relq = nullptr;
|
||||
size_t relqlen = 0;
|
||||
auto uri = link.uri.first;
|
||||
auto len = link.uri.second - link.uri.first;
|
||||
|
||||
http_parser_url v{};
|
||||
rv = http_parser_parse_url(link_url, link_urllen, 0, &v);
|
||||
std::string scheme, authority, path;
|
||||
|
||||
rv = http2::construct_push_component(scheme, authority, path, base,
|
||||
baselen, uri, len);
|
||||
if (rv != 0) {
|
||||
assert(link_urllen);
|
||||
if (link_url[0] == '/') {
|
||||
continue;
|
||||
}
|
||||
// treat link_url as relative URI.
|
||||
auto end = std::find(link_url, link_url + link_urllen, '#');
|
||||
auto q = std::find(link_url, end, '?');
|
||||
rel = link_url;
|
||||
rellen = q - link_url;
|
||||
if (q != end) {
|
||||
relq = q + 1;
|
||||
relqlen = end - relq;
|
||||
}
|
||||
} else {
|
||||
if (v.field_set & (1 << UF_HOST)) {
|
||||
continue;
|
||||
}
|
||||
if (v.field_set & (1 << UF_PATH)) {
|
||||
auto &f = v.field_data[UF_PATH];
|
||||
rel = link_url + f.off;
|
||||
rellen = f.len;
|
||||
} else {
|
||||
rel = "/";
|
||||
rellen = 1;
|
||||
}
|
||||
|
||||
if (v.field_set & (1 << UF_QUERY)) {
|
||||
auto &f = v.field_data[UF_QUERY];
|
||||
relq = link_url + f.off;
|
||||
relqlen = f.len;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
auto path = http2::path_join(base, baselen, nullptr, 0, rel, rellen, relq,
|
||||
relqlen);
|
||||
rv = submit_push_promise(path, downstream);
|
||||
|
||||
if (scheme.empty()) {
|
||||
scheme = downstream->get_request_http2_scheme();
|
||||
}
|
||||
|
||||
if (authority.empty()) {
|
||||
authority = downstream->get_request_http2_authority();
|
||||
}
|
||||
|
||||
rv = submit_push_promise(scheme, authority, path, downstream);
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
@@ -1547,7 +1642,9 @@ int Http2Upstream::prepare_push_promise(Downstream *downstream) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Http2Upstream::submit_push_promise(const std::string &path,
|
||||
int Http2Upstream::submit_push_promise(const std::string &scheme,
|
||||
const std::string &authority,
|
||||
const std::string &path,
|
||||
Downstream *downstream) {
|
||||
int rv;
|
||||
std::vector<nghttp2_nv> nva;
|
||||
@@ -1555,13 +1652,9 @@ int Http2Upstream::submit_push_promise(const std::string &path,
|
||||
|
||||
// juse use "GET" for now
|
||||
nva.push_back(http2::make_nv_ll(":method", "GET"));
|
||||
nva.push_back(
|
||||
http2::make_nv_ls(":scheme", downstream->get_request_http2_scheme()));
|
||||
nva.push_back(http2::make_nv_ls(":scheme", scheme));
|
||||
nva.push_back(http2::make_nv_ls(":path", path));
|
||||
auto &authority = downstream->get_request_http2_authority();
|
||||
if (!authority.empty()) {
|
||||
nva.push_back(http2::make_nv_ls(":authority", authority));
|
||||
}
|
||||
nva.push_back(http2::make_nv_ls(":authority", authority));
|
||||
|
||||
for (auto &kv : downstream->get_request_headers()) {
|
||||
switch (kv.token) {
|
||||
@@ -1608,4 +1701,50 @@ int Http2Upstream::submit_push_promise(const std::string &path,
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Http2Upstream::initiate_push(Downstream *downstream, const char *uri,
|
||||
size_t len) {
|
||||
int rv;
|
||||
|
||||
if (len == 0 || get_config()->no_server_push ||
|
||||
nghttp2_session_get_remote_settings(session_,
|
||||
NGHTTP2_SETTINGS_ENABLE_PUSH) == 0 ||
|
||||
get_config()->http2_proxy || get_config()->client_proxy ||
|
||||
(downstream->get_stream_id() % 2) == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *base;
|
||||
size_t baselen;
|
||||
|
||||
rv = http2::get_pure_path_component(&base, &baselen,
|
||||
downstream->get_request_path());
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string scheme, authority, path;
|
||||
|
||||
rv = http2::construct_push_component(scheme, authority, path, base, baselen,
|
||||
uri, len);
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (scheme.empty()) {
|
||||
scheme = downstream->get_request_http2_scheme();
|
||||
}
|
||||
|
||||
if (authority.empty()) {
|
||||
authority = downstream->get_request_http2_authority();
|
||||
}
|
||||
|
||||
rv = submit_push_promise(scheme, authority, path, downstream);
|
||||
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace shrpx
|
||||
|
||||
@@ -78,6 +78,10 @@ public:
|
||||
|
||||
virtual void on_handler_delete();
|
||||
virtual int on_downstream_reset(bool no_retry);
|
||||
virtual int send_reply(Downstream *downstream, const uint8_t *body,
|
||||
size_t bodylen);
|
||||
virtual int initiate_push(Downstream *downstream, const char *uri,
|
||||
size_t len);
|
||||
|
||||
bool get_flow_control() const;
|
||||
// Perform HTTP/2 upgrade from |upstream|. On success, this object
|
||||
@@ -96,7 +100,9 @@ public:
|
||||
void check_shutdown();
|
||||
|
||||
int prepare_push_promise(Downstream *downstream);
|
||||
int submit_push_promise(const std::string &path, Downstream *downstream);
|
||||
int submit_push_promise(const std::string &scheme,
|
||||
const std::string &authority, const std::string &path,
|
||||
Downstream *downstream);
|
||||
|
||||
int on_request_headers(Downstream *downstream, const nghttp2_frame *frame);
|
||||
|
||||
|
||||
@@ -209,188 +209,155 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
|
||||
}
|
||||
|
||||
int HttpDownstreamConnection::push_request_headers() {
|
||||
const char *authority = nullptr, *host = nullptr;
|
||||
auto downstream_hostport = get_config()
|
||||
->downstream_addr_groups[group_]
|
||||
.addrs[addr_idx_]
|
||||
.hostport.get();
|
||||
auto connect_method = downstream_->get_request_method() == HTTP_CONNECT;
|
||||
auto method = downstream_->get_request_method();
|
||||
auto connect_method = method == HTTP_CONNECT;
|
||||
|
||||
if (!get_config()->no_host_rewrite && !get_config()->http2_proxy &&
|
||||
!get_config()->client_proxy && !connect_method) {
|
||||
if (!downstream_->get_request_http2_authority().empty()) {
|
||||
authority = downstream_hostport;
|
||||
}
|
||||
if (downstream_->get_request_header(http2::HD_HOST)) {
|
||||
host = downstream_hostport;
|
||||
}
|
||||
} else {
|
||||
if (!downstream_->get_request_http2_authority().empty()) {
|
||||
authority = downstream_->get_request_http2_authority().c_str();
|
||||
}
|
||||
auto h = downstream_->get_request_header(http2::HD_HOST);
|
||||
if (h) {
|
||||
host = h->value.c_str();
|
||||
}
|
||||
}
|
||||
// For HTTP/1.0 request, there is no authority in request. In that
|
||||
// case, we use backend server's host nonetheless.
|
||||
const char *authority = downstream_hostport;
|
||||
auto &req_authority = downstream_->get_request_http2_authority();
|
||||
auto no_host_rewrite = get_config()->no_host_rewrite ||
|
||||
get_config()->http2_proxy ||
|
||||
get_config()->client_proxy || connect_method;
|
||||
|
||||
if (!authority && !host) {
|
||||
// upstream is HTTP/1.0. We use backend server's host
|
||||
// nonetheless.
|
||||
host = downstream_hostport;
|
||||
if (no_host_rewrite && !req_authority.empty()) {
|
||||
authority = req_authority.c_str();
|
||||
}
|
||||
auto authoritylen = strlen(authority);
|
||||
|
||||
if (authority) {
|
||||
downstream_->set_request_downstream_host(authority);
|
||||
} else {
|
||||
downstream_->set_request_downstream_host(host);
|
||||
}
|
||||
downstream_->set_request_downstream_host(authority);
|
||||
|
||||
downstream_->assemble_request_cookie();
|
||||
|
||||
auto buf = downstream_->get_request_buf();
|
||||
|
||||
// Assume that method and request path do not contain \r\n.
|
||||
std::string hdrs = http2::to_method_string(downstream_->get_request_method());
|
||||
hdrs += ' ';
|
||||
auto meth = http2::to_method_string(method);
|
||||
buf->append(meth, strlen(meth));
|
||||
buf->append(" ");
|
||||
|
||||
auto &scheme = downstream_->get_request_http2_scheme();
|
||||
auto &path = downstream_->get_request_path();
|
||||
|
||||
if (connect_method) {
|
||||
if (authority) {
|
||||
hdrs += authority;
|
||||
} else {
|
||||
hdrs += downstream_->get_request_path();
|
||||
}
|
||||
buf->append(authority, authoritylen);
|
||||
} else if (get_config()->http2_proxy || get_config()->client_proxy) {
|
||||
// Construct absolute-form request target because we are going to
|
||||
// send a request to a HTTP/1 proxy.
|
||||
assert(!scheme.empty());
|
||||
hdrs += scheme;
|
||||
hdrs += "://";
|
||||
|
||||
if (authority) {
|
||||
hdrs += authority;
|
||||
} else {
|
||||
hdrs += host;
|
||||
}
|
||||
|
||||
// Server-wide OPTIONS takes following form in proxy request:
|
||||
//
|
||||
// OPTIONS http://example.org HTTP/1.1
|
||||
//
|
||||
// Notice that no slash after authority. See
|
||||
// http://tools.ietf.org/html/rfc7230#section-5.3.4
|
||||
if (downstream_->get_request_path() != "*") {
|
||||
hdrs += downstream_->get_request_path();
|
||||
}
|
||||
buf->append(scheme);
|
||||
buf->append("://");
|
||||
buf->append(authority, authoritylen);
|
||||
buf->append(path);
|
||||
} else if (method == HTTP_OPTIONS && path.empty()) {
|
||||
// Server-wide OPTIONS
|
||||
buf->append("*");
|
||||
} else {
|
||||
// No proxy case.
|
||||
hdrs += downstream_->get_request_path();
|
||||
buf->append(path);
|
||||
}
|
||||
hdrs += " HTTP/1.1\r\nHost: ";
|
||||
if (authority) {
|
||||
hdrs += authority;
|
||||
} else {
|
||||
hdrs += host;
|
||||
}
|
||||
hdrs += "\r\n";
|
||||
buf->append(" HTTP/1.1\r\nHost: ");
|
||||
buf->append(authority, authoritylen);
|
||||
buf->append("\r\n");
|
||||
|
||||
http2::build_http1_headers_from_headers(hdrs,
|
||||
http2::build_http1_headers_from_headers(buf,
|
||||
downstream_->get_request_headers());
|
||||
|
||||
if (!downstream_->get_assembled_request_cookie().empty()) {
|
||||
hdrs += "Cookie: ";
|
||||
hdrs += downstream_->get_assembled_request_cookie();
|
||||
hdrs += "\r\n";
|
||||
buf->append("Cookie: ");
|
||||
buf->append(downstream_->get_assembled_request_cookie());
|
||||
buf->append("\r\n");
|
||||
}
|
||||
|
||||
if (!connect_method && downstream_->get_request_http2_expect_body() &&
|
||||
!downstream_->get_request_header(http2::HD_CONTENT_LENGTH)) {
|
||||
|
||||
downstream_->set_chunked_request(true);
|
||||
hdrs += "Transfer-Encoding: chunked\r\n";
|
||||
buf->append("Transfer-Encoding: chunked\r\n");
|
||||
}
|
||||
|
||||
if (downstream_->get_request_connection_close()) {
|
||||
hdrs += "Connection: close\r\n";
|
||||
buf->append("Connection: close\r\n");
|
||||
}
|
||||
|
||||
if (!connect_method && downstream_->get_upgrade_request()) {
|
||||
auto connection = downstream_->get_request_header(http2::HD_CONNECTION);
|
||||
if (connection) {
|
||||
hdrs += "Connection: ";
|
||||
hdrs += (*connection).value;
|
||||
hdrs += "\r\n";
|
||||
buf->append("Connection: ");
|
||||
buf->append((*connection).value);
|
||||
buf->append("\r\n");
|
||||
}
|
||||
|
||||
auto upgrade = downstream_->get_request_header(http2::HD_UPGRADE);
|
||||
if (upgrade) {
|
||||
hdrs += "Upgrade: ";
|
||||
hdrs += (*upgrade).value;
|
||||
hdrs += "\r\n";
|
||||
buf->append("Upgrade: ");
|
||||
buf->append((*upgrade).value);
|
||||
buf->append("\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
auto xff = downstream_->get_request_header(http2::HD_X_FORWARDED_FOR);
|
||||
if (get_config()->add_x_forwarded_for) {
|
||||
hdrs += "X-Forwarded-For: ";
|
||||
buf->append("X-Forwarded-For: ");
|
||||
if (xff && !get_config()->strip_incoming_x_forwarded_for) {
|
||||
hdrs += (*xff).value;
|
||||
hdrs += ", ";
|
||||
buf->append((*xff).value);
|
||||
buf->append(", ");
|
||||
}
|
||||
hdrs += client_handler_->get_ipaddr();
|
||||
hdrs += "\r\n";
|
||||
buf->append(client_handler_->get_ipaddr());
|
||||
buf->append("\r\n");
|
||||
} else if (xff && !get_config()->strip_incoming_x_forwarded_for) {
|
||||
hdrs += "X-Forwarded-For: ";
|
||||
hdrs += (*xff).value;
|
||||
hdrs += "\r\n";
|
||||
buf->append("X-Forwarded-For: ");
|
||||
buf->append((*xff).value);
|
||||
buf->append("\r\n");
|
||||
}
|
||||
if (!get_config()->http2_proxy && !get_config()->client_proxy &&
|
||||
!connect_method) {
|
||||
hdrs += "X-Forwarded-Proto: ";
|
||||
buf->append("X-Forwarded-Proto: ");
|
||||
assert(!scheme.empty());
|
||||
hdrs += scheme;
|
||||
hdrs += "\r\n";
|
||||
buf->append(scheme);
|
||||
buf->append("\r\n");
|
||||
}
|
||||
auto via = downstream_->get_request_header(http2::HD_VIA);
|
||||
if (get_config()->no_via) {
|
||||
if (via) {
|
||||
hdrs += "Via: ";
|
||||
hdrs += (*via).value;
|
||||
hdrs += "\r\n";
|
||||
buf->append("Via: ");
|
||||
buf->append((*via).value);
|
||||
buf->append("\r\n");
|
||||
}
|
||||
} else {
|
||||
hdrs += "Via: ";
|
||||
buf->append("Via: ");
|
||||
if (via) {
|
||||
hdrs += (*via).value;
|
||||
hdrs += ", ";
|
||||
buf->append((*via).value);
|
||||
buf->append(", ");
|
||||
}
|
||||
hdrs += http::create_via_header_value(downstream_->get_request_major(),
|
||||
downstream_->get_request_minor());
|
||||
hdrs += "\r\n";
|
||||
buf->append(http::create_via_header_value(
|
||||
downstream_->get_request_major(), downstream_->get_request_minor()));
|
||||
buf->append("\r\n");
|
||||
}
|
||||
|
||||
for (auto &p : get_config()->add_request_headers) {
|
||||
hdrs += p.first;
|
||||
hdrs += ": ";
|
||||
hdrs += p.second;
|
||||
hdrs += "\r\n";
|
||||
buf->append(p.first);
|
||||
buf->append(": ");
|
||||
buf->append(p.second);
|
||||
buf->append("\r\n");
|
||||
}
|
||||
|
||||
hdrs += "\r\n";
|
||||
buf->append("\r\n");
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
const char *hdrp;
|
||||
std::string nhdrs;
|
||||
for (auto chunk = buf->head; chunk; chunk = chunk->next) {
|
||||
nhdrs.append(chunk->pos, chunk->last);
|
||||
}
|
||||
if (log_config()->errorlog_tty) {
|
||||
nhdrs = http::colorizeHeaders(hdrs.c_str());
|
||||
hdrp = nhdrs.c_str();
|
||||
} else {
|
||||
hdrp = hdrs.c_str();
|
||||
nhdrs = http::colorizeHeaders(nhdrs.c_str());
|
||||
}
|
||||
DCLOG(INFO, this) << "HTTP request headers. stream_id="
|
||||
<< downstream_->get_stream_id() << "\n" << hdrp;
|
||||
<< downstream_->get_stream_id() << "\n" << nhdrs;
|
||||
}
|
||||
auto output = downstream_->get_request_buf();
|
||||
output->append(hdrs.c_str(), hdrs.size());
|
||||
|
||||
signal_write();
|
||||
|
||||
@@ -430,9 +397,7 @@ int HttpDownstreamConnection::end_upload_data() {
|
||||
output->append("0\r\n\r\n");
|
||||
} else {
|
||||
output->append("0\r\n");
|
||||
std::string trailer_part;
|
||||
http2::build_http1_headers_from_headers(trailer_part, trailers);
|
||||
output->append(trailer_part.c_str(), trailer_part.size());
|
||||
http2::build_http1_headers_from_headers(output, trailers);
|
||||
output->append("\r\n");
|
||||
}
|
||||
|
||||
@@ -774,6 +739,12 @@ int HttpDownstreamConnection::on_read() {
|
||||
auto htperr = HTTP_PARSER_ERRNO(&response_htp_);
|
||||
|
||||
if (htperr != HPE_OK) {
|
||||
// Handling early return (in other words, response was hijacked
|
||||
// by mruby scripting).
|
||||
if (downstream_->get_response_state() == Downstream::MSG_COMPLETE) {
|
||||
return SHRPX_ERR_DCONN_CANCELED;
|
||||
}
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DCLOG(INFO, this) << "HTTP parser failure: "
|
||||
<< "(" << http_errno_name(htperr) << ") "
|
||||
|
||||
@@ -37,6 +37,9 @@
|
||||
#include "shrpx_log_config.h"
|
||||
#include "shrpx_worker.h"
|
||||
#include "shrpx_http2_session.h"
|
||||
#ifdef HAVE_MRUBY
|
||||
#include "shrpx_mruby.h"
|
||||
#endif // HAVE_MRUBY
|
||||
#include "http2.h"
|
||||
#include "util.h"
|
||||
#include "template.h"
|
||||
@@ -69,8 +72,14 @@ int htp_msg_begin(http_parser *htp) {
|
||||
auto handler = upstream->get_client_handler();
|
||||
|
||||
// TODO specify 0 as priority for now
|
||||
upstream->attach_downstream(
|
||||
make_unique<Downstream>(upstream, handler->get_mcpool(), 0, 0));
|
||||
auto downstream =
|
||||
make_unique<Downstream>(upstream, handler->get_mcpool(), 0, 0);
|
||||
|
||||
// We happen to have the same value for method token.
|
||||
downstream->set_request_method(htp->method);
|
||||
|
||||
upstream->attach_downstream(std::move(downstream));
|
||||
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
@@ -90,7 +99,12 @@ int htp_uricb(http_parser *htp, const char *data, size_t len) {
|
||||
return -1;
|
||||
}
|
||||
downstream->add_request_headers_sum(len);
|
||||
downstream->append_request_path(data, len);
|
||||
if (downstream->get_request_method() == HTTP_CONNECT) {
|
||||
downstream->append_request_http2_authority(data, len);
|
||||
} else {
|
||||
downstream->append_request_path(data, len);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
@@ -198,6 +212,10 @@ void rewrite_request_host_path_from_uri(Downstream *downstream, const char *uri,
|
||||
}
|
||||
downstream->set_request_http2_authority(authority);
|
||||
|
||||
std::string scheme;
|
||||
http2::copy_url_component(scheme, &u, UF_SCHEMA, uri);
|
||||
downstream->set_request_http2_scheme(std::move(scheme));
|
||||
|
||||
std::string path;
|
||||
if (u.field_set & (1 << UF_PATH)) {
|
||||
http2::copy_url_component(path, &u, UF_PATH, uri);
|
||||
@@ -208,7 +226,7 @@ void rewrite_request_host_path_from_uri(Downstream *downstream, const char *uri,
|
||||
//
|
||||
// Notice that no slash after authority. See
|
||||
// http://tools.ietf.org/html/rfc7230#section-5.3.4
|
||||
downstream->set_request_path("*");
|
||||
downstream->set_request_path("");
|
||||
// we ignore query component here
|
||||
return;
|
||||
} else {
|
||||
@@ -225,10 +243,6 @@ void rewrite_request_host_path_from_uri(Downstream *downstream, const char *uri,
|
||||
downstream->set_request_path(
|
||||
http2::rewrite_clean_path(std::begin(path), std::end(path)));
|
||||
}
|
||||
|
||||
std::string scheme;
|
||||
http2::copy_url_component(scheme, &u, UF_SCHEMA, uri);
|
||||
downstream->set_request_http2_scheme(std::move(scheme));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -241,17 +255,18 @@ int htp_hdrs_completecb(http_parser *htp) {
|
||||
}
|
||||
auto downstream = upstream->get_downstream();
|
||||
|
||||
// We happen to have the same value for method token.
|
||||
downstream->set_request_method(htp->method);
|
||||
downstream->set_request_major(htp->http_major);
|
||||
downstream->set_request_minor(htp->http_minor);
|
||||
|
||||
downstream->set_request_connection_close(!http_should_keep_alive(htp));
|
||||
|
||||
auto method = downstream->get_request_method();
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
std::stringstream ss;
|
||||
ss << http2::to_method_string(downstream->get_request_method()) << " "
|
||||
<< downstream->get_request_path() << " "
|
||||
ss << http2::to_method_string(method) << " "
|
||||
<< (method == HTTP_CONNECT ? downstream->get_request_http2_authority()
|
||||
: downstream->get_request_path()) << " "
|
||||
<< "HTTP/" << downstream->get_request_major() << "."
|
||||
<< downstream->get_request_minor() << "\n";
|
||||
const auto &headers = downstream->get_request_headers();
|
||||
@@ -274,13 +289,12 @@ int htp_hdrs_completecb(http_parser *htp) {
|
||||
|
||||
downstream->inspect_http1_request();
|
||||
|
||||
if (downstream->get_request_method() != HTTP_CONNECT) {
|
||||
if (method != HTTP_CONNECT) {
|
||||
http_parser_url u{};
|
||||
// make a copy of request path, since we may set request path
|
||||
// while we are refering to original request path.
|
||||
auto uri = downstream->get_request_path();
|
||||
rv = http_parser_parse_url(uri.c_str(),
|
||||
downstream->get_request_path().size(), 0, &u);
|
||||
auto path = downstream->get_request_path();
|
||||
rv = http_parser_parse_url(path.c_str(), path.size(), 0, &u);
|
||||
if (rv != 0) {
|
||||
// Expect to respond with 400 bad request
|
||||
return -1;
|
||||
@@ -292,8 +306,17 @@ int htp_hdrs_completecb(http_parser *htp) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
downstream->set_request_path(
|
||||
http2::rewrite_clean_path(std::begin(uri), std::end(uri)));
|
||||
if (method == HTTP_OPTIONS && path == "*") {
|
||||
downstream->set_request_path("");
|
||||
} else {
|
||||
downstream->set_request_path(
|
||||
http2::rewrite_clean_path(std::begin(path), std::end(path)));
|
||||
}
|
||||
|
||||
auto host = downstream->get_request_header(http2::HD_HOST);
|
||||
if (host) {
|
||||
downstream->set_request_http2_authority(host->value);
|
||||
}
|
||||
|
||||
if (upstream->get_client_handler()->get_ssl()) {
|
||||
downstream->set_request_http2_scheme("https");
|
||||
@@ -301,10 +324,29 @@ int htp_hdrs_completecb(http_parser *htp) {
|
||||
downstream->set_request_http2_scheme("http");
|
||||
}
|
||||
} else {
|
||||
rewrite_request_host_path_from_uri(downstream, uri.c_str(), u);
|
||||
rewrite_request_host_path_from_uri(downstream, path.c_str(), u);
|
||||
}
|
||||
}
|
||||
|
||||
downstream->set_request_state(Downstream::HEADER_COMPLETE);
|
||||
|
||||
#ifdef HAVE_MRUBY
|
||||
auto handler = upstream->get_client_handler();
|
||||
auto worker = handler->get_worker();
|
||||
auto mruby_ctx = worker->get_mruby_context();
|
||||
|
||||
if (mruby_ctx->run_on_request_proc(downstream) != 0) {
|
||||
downstream->set_response_http_status(500);
|
||||
return -1;
|
||||
}
|
||||
#endif // HAVE_MRUBY
|
||||
|
||||
// mruby hook may change method value
|
||||
|
||||
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
rv = downstream->attach_downstream_connection(
|
||||
upstream->get_client_handler()->get_downstream_connection(downstream));
|
||||
|
||||
@@ -320,8 +362,6 @@ int htp_hdrs_completecb(http_parser *htp) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
downstream->set_request_state(Downstream::HEADER_COMPLETE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
@@ -352,6 +392,17 @@ int htp_msg_completecb(http_parser *htp) {
|
||||
downstream->set_request_state(Downstream::MSG_COMPLETE);
|
||||
rv = downstream->end_upload_data();
|
||||
if (rv != 0) {
|
||||
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
|
||||
// Here both response and request were completed. One of the
|
||||
// reason why end_upload_data() failed is when we sent response
|
||||
// in request phase hook. We only delete and proceed to the
|
||||
// next request handling (if we don't close the connection). We
|
||||
// first pause parser here jsut as we normally do, and call
|
||||
// signal_write() to run on_write().
|
||||
http_parser_pause(htp, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -451,6 +502,13 @@ int HttpsUpstream::on_read() {
|
||||
auto htperr = HTTP_PARSER_ERRNO(&htp_);
|
||||
|
||||
if (htperr == HPE_PAUSED) {
|
||||
// We may pause parser in htp_msg_completecb when both side are
|
||||
// completed. Signal write, so that we can run on_write().
|
||||
if (downstream &&
|
||||
downstream->get_request_state() == Downstream::MSG_COMPLETE &&
|
||||
downstream->get_response_state() == Downstream::MSG_COMPLETE) {
|
||||
handler_->signal_write();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -472,13 +530,16 @@ int HttpsUpstream::on_read() {
|
||||
if (htperr == HPE_INVALID_METHOD) {
|
||||
status_code = 501;
|
||||
} else if (downstream) {
|
||||
if (downstream->get_request_state() == Downstream::CONNECT_FAIL) {
|
||||
status_code = 503;
|
||||
} else if (downstream->get_request_state() ==
|
||||
Downstream::HTTP1_REQUEST_HEADER_TOO_LARGE) {
|
||||
status_code = 431;
|
||||
} else {
|
||||
status_code = 400;
|
||||
status_code = downstream->get_response_http_status();
|
||||
if (status_code == 0) {
|
||||
if (downstream->get_request_state() == Downstream::CONNECT_FAIL) {
|
||||
status_code = 503;
|
||||
} else if (downstream->get_request_state() ==
|
||||
Downstream::HTTP1_REQUEST_HEADER_TOO_LARGE) {
|
||||
status_code = 431;
|
||||
} else {
|
||||
status_code = 400;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
status_code = 400;
|
||||
@@ -552,6 +613,11 @@ int HttpsUpstream::on_write() {
|
||||
// We need this if response ends before request.
|
||||
if (downstream->get_request_state() == Downstream::MSG_COMPLETE) {
|
||||
delete_downstream();
|
||||
|
||||
if (handler_->get_should_close_after_write()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return resume_read(SHRPX_NO_BUFFER, nullptr, 0);
|
||||
}
|
||||
}
|
||||
@@ -594,6 +660,11 @@ int HttpsUpstream::downstream_read(DownstreamConnection *dconn) {
|
||||
return downstream_eof(dconn);
|
||||
}
|
||||
|
||||
if (rv == SHRPX_ERR_DCONN_CANCELED) {
|
||||
downstream->pop_downstream_connection();
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (rv < 0) {
|
||||
return downstream_error(dconn, Downstream::EVENT_ERROR);
|
||||
}
|
||||
@@ -703,6 +774,60 @@ int HttpsUpstream::downstream_error(DownstreamConnection *dconn, int events) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HttpsUpstream::send_reply(Downstream *downstream, const uint8_t *body,
|
||||
size_t bodylen) {
|
||||
auto major = downstream->get_request_major();
|
||||
auto minor = downstream->get_request_minor();
|
||||
|
||||
auto connection_close = false;
|
||||
if (major <= 0 || (major == 1 && minor == 0)) {
|
||||
connection_close = true;
|
||||
} else {
|
||||
auto c = downstream->get_response_header(http2::HD_CONNECTION);
|
||||
if (c && util::strieq_l("close", c->value)) {
|
||||
connection_close = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (connection_close) {
|
||||
downstream->set_response_connection_close(true);
|
||||
handler_->set_should_close_after_write(true);
|
||||
}
|
||||
|
||||
auto output = downstream->get_response_buf();
|
||||
|
||||
output->append("HTTP/1.1 ");
|
||||
output->append(
|
||||
http2::get_status_string(downstream->get_response_http_status()));
|
||||
output->append("\r\n");
|
||||
|
||||
for (auto &kv : downstream->get_response_headers()) {
|
||||
if (kv.name.empty() || kv.name[0] == ':') {
|
||||
continue;
|
||||
}
|
||||
http2::capitalize(output, kv.name);
|
||||
output->append(": ");
|
||||
output->append(kv.value);
|
||||
output->append("\r\n");
|
||||
}
|
||||
|
||||
if (!downstream->get_response_header(http2::HD_SERVER)) {
|
||||
output->append("Server: ");
|
||||
output->append(get_config()->server_name,
|
||||
strlen(get_config()->server_name));
|
||||
output->append("\r\n");
|
||||
}
|
||||
|
||||
output->append("\r\n");
|
||||
|
||||
output->append(body, bodylen);
|
||||
|
||||
downstream->add_response_sent_bodylen(bodylen);
|
||||
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void HttpsUpstream::error_reply(unsigned int status_code) {
|
||||
auto html = http::create_error_html(status_code);
|
||||
auto downstream = get_downstream();
|
||||
@@ -729,6 +854,11 @@ void HttpsUpstream::error_reply(unsigned int status_code) {
|
||||
output->append("\r\nContent-Length: ");
|
||||
auto cl = util::utos(html.size());
|
||||
output->append(cl.c_str(), cl.size());
|
||||
output->append("\r\nDate: ");
|
||||
auto lgconf = log_config();
|
||||
lgconf->update_tstamp(std::chrono::system_clock::now());
|
||||
auto &date = lgconf->time_http_str;
|
||||
output->append(date.c_str(), date.size());
|
||||
output->append("\r\nContent-Type: text/html; "
|
||||
"charset=UTF-8\r\nConnection: close\r\n\r\n");
|
||||
output->append(html.c_str(), html.size());
|
||||
@@ -756,6 +886,17 @@ std::unique_ptr<Downstream> HttpsUpstream::pop_downstream() {
|
||||
return std::unique_ptr<Downstream>(downstream_.release());
|
||||
}
|
||||
|
||||
namespace {
|
||||
void write_altsvc(DefaultMemchunks *buf, const AltSvc &altsvc) {
|
||||
buf->append(util::percent_encode_token(altsvc.protocol_id));
|
||||
buf->append("=\"");
|
||||
buf->append(util::quote_string(altsvc.host));
|
||||
buf->append(":");
|
||||
buf->append(altsvc.service);
|
||||
buf->append("\"");
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
if (downstream->get_non_final_response()) {
|
||||
@@ -765,15 +906,33 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_MRUBY
|
||||
if (!downstream->get_non_final_response()) {
|
||||
auto worker = handler_->get_worker();
|
||||
auto mruby_ctx = worker->get_mruby_context();
|
||||
|
||||
if (mruby_ctx->run_on_response_proc(downstream) != 0) {
|
||||
error_reply(500);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
#endif // HAVE_MRUBY
|
||||
|
||||
auto connect_method = downstream->get_request_method() == HTTP_CONNECT;
|
||||
|
||||
std::string hdrs = "HTTP/";
|
||||
hdrs += util::utos(downstream->get_request_major());
|
||||
hdrs += ".";
|
||||
hdrs += util::utos(downstream->get_request_minor());
|
||||
hdrs += " ";
|
||||
hdrs += http2::get_status_string(downstream->get_response_http_status());
|
||||
hdrs += "\r\n";
|
||||
auto buf = downstream->get_response_buf();
|
||||
|
||||
buf->append("HTTP/");
|
||||
buf->append(util::utos(downstream->get_request_major()));
|
||||
buf->append(".");
|
||||
buf->append(util::utos(downstream->get_request_minor()));
|
||||
buf->append(" ");
|
||||
buf->append(http2::get_status_string(downstream->get_response_http_status()));
|
||||
buf->append("\r\n");
|
||||
|
||||
if (!get_config()->http2_proxy && !get_config()->client_proxy &&
|
||||
!get_config()->no_location_rewrite) {
|
||||
@@ -781,20 +940,16 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
||||
get_client_handler()->get_upstream_scheme());
|
||||
}
|
||||
|
||||
http2::build_http1_headers_from_headers(hdrs,
|
||||
http2::build_http1_headers_from_headers(buf,
|
||||
downstream->get_response_headers());
|
||||
|
||||
auto output = downstream->get_response_buf();
|
||||
|
||||
if (downstream->get_non_final_response()) {
|
||||
hdrs += "\r\n";
|
||||
buf->append("\r\n");
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
log_response_headers(hdrs);
|
||||
log_response_headers(buf);
|
||||
}
|
||||
|
||||
output->append(hdrs.c_str(), hdrs.size());
|
||||
|
||||
downstream->clear_response_headers();
|
||||
|
||||
return 0;
|
||||
@@ -815,93 +970,87 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
||||
if (downstream->get_request_major() <= 0 ||
|
||||
downstream->get_request_minor() <= 0) {
|
||||
// We add this header for HTTP/1.0 or HTTP/0.9 clients
|
||||
hdrs += "Connection: Keep-Alive\r\n";
|
||||
buf->append("Connection: Keep-Alive\r\n");
|
||||
}
|
||||
} else if (!downstream->get_upgraded()) {
|
||||
hdrs += "Connection: close\r\n";
|
||||
buf->append("Connection: close\r\n");
|
||||
}
|
||||
|
||||
if (!connect_method && downstream->get_upgraded()) {
|
||||
auto connection = downstream->get_response_header(http2::HD_CONNECTION);
|
||||
if (connection) {
|
||||
hdrs += "Connection: ";
|
||||
hdrs += (*connection).value;
|
||||
hdrs += "\r\n";
|
||||
buf->append("Connection: ");
|
||||
buf->append((*connection).value);
|
||||
buf->append("\r\n");
|
||||
}
|
||||
|
||||
auto upgrade = downstream->get_response_header(http2::HD_UPGRADE);
|
||||
if (upgrade) {
|
||||
hdrs += "Upgrade: ";
|
||||
hdrs += (*upgrade).value;
|
||||
hdrs += "\r\n";
|
||||
buf->append("Upgrade: ");
|
||||
buf->append((*upgrade).value);
|
||||
buf->append("\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (!downstream->get_response_header(http2::HD_ALT_SVC)) {
|
||||
// We won't change or alter alt-svc from backend for now
|
||||
if (!get_config()->altsvcs.empty()) {
|
||||
hdrs += "Alt-Svc: ";
|
||||
buf->append("Alt-Svc: ");
|
||||
|
||||
for (const auto &altsvc : get_config()->altsvcs) {
|
||||
hdrs += util::percent_encode_token(altsvc.protocol_id);
|
||||
hdrs += "=\"";
|
||||
hdrs += util::quote_string(altsvc.host);
|
||||
hdrs += ":";
|
||||
hdrs += altsvc.service;
|
||||
hdrs += "\", ";
|
||||
auto &altsvcs = get_config()->altsvcs;
|
||||
write_altsvc(buf, altsvcs[0]);
|
||||
for (size_t i = 1; i < altsvcs.size(); ++i) {
|
||||
buf->append(", ");
|
||||
write_altsvc(buf, altsvcs[i]);
|
||||
}
|
||||
|
||||
hdrs[hdrs.size() - 2] = '\r';
|
||||
hdrs[hdrs.size() - 1] = '\n';
|
||||
buf->append("\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (!get_config()->http2_proxy && !get_config()->client_proxy) {
|
||||
hdrs += "Server: ";
|
||||
hdrs += get_config()->server_name;
|
||||
hdrs += "\r\n";
|
||||
buf->append("Server: ");
|
||||
buf->append(get_config()->server_name, strlen(get_config()->server_name));
|
||||
buf->append("\r\n");
|
||||
} else {
|
||||
auto server = downstream->get_response_header(http2::HD_SERVER);
|
||||
if (server) {
|
||||
hdrs += "Server: ";
|
||||
hdrs += (*server).value;
|
||||
hdrs += "\r\n";
|
||||
buf->append("Server: ");
|
||||
buf->append((*server).value);
|
||||
buf->append("\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
auto via = downstream->get_response_header(http2::HD_VIA);
|
||||
if (get_config()->no_via) {
|
||||
if (via) {
|
||||
hdrs += "Via: ";
|
||||
hdrs += (*via).value;
|
||||
hdrs += "\r\n";
|
||||
buf->append("Via: ");
|
||||
buf->append((*via).value);
|
||||
buf->append("\r\n");
|
||||
}
|
||||
} else {
|
||||
hdrs += "Via: ";
|
||||
buf->append("Via: ");
|
||||
if (via) {
|
||||
hdrs += (*via).value;
|
||||
hdrs += ", ";
|
||||
buf->append((*via).value);
|
||||
buf->append(", ");
|
||||
}
|
||||
hdrs += http::create_via_header_value(downstream->get_response_major(),
|
||||
downstream->get_response_minor());
|
||||
hdrs += "\r\n";
|
||||
buf->append(http::create_via_header_value(
|
||||
downstream->get_response_major(), downstream->get_response_minor()));
|
||||
buf->append("\r\n");
|
||||
}
|
||||
|
||||
for (auto &p : get_config()->add_response_headers) {
|
||||
hdrs += p.first;
|
||||
hdrs += ": ";
|
||||
hdrs += p.second;
|
||||
hdrs += "\r\n";
|
||||
buf->append(p.first);
|
||||
buf->append(": ");
|
||||
buf->append(p.second);
|
||||
buf->append("\r\n");
|
||||
}
|
||||
|
||||
hdrs += "\r\n";
|
||||
buf->append("\r\n");
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
log_response_headers(hdrs);
|
||||
log_response_headers(buf);
|
||||
}
|
||||
|
||||
output->append(hdrs.c_str(), hdrs.size());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -936,9 +1085,7 @@ int HttpsUpstream::on_downstream_body_complete(Downstream *downstream) {
|
||||
output->append("0\r\n\r\n");
|
||||
} else {
|
||||
output->append("0\r\n");
|
||||
std::string trailer_part;
|
||||
http2::build_http1_headers_from_headers(trailer_part, trailers);
|
||||
output->append(trailer_part.c_str(), trailer_part.size());
|
||||
http2::build_http1_headers_from_headers(output, trailers);
|
||||
output->append("\r\n");
|
||||
}
|
||||
}
|
||||
@@ -965,16 +1112,15 @@ int HttpsUpstream::on_downstream_abort_request(Downstream *downstream,
|
||||
return 0;
|
||||
}
|
||||
|
||||
void HttpsUpstream::log_response_headers(const std::string &hdrs) const {
|
||||
const char *hdrp;
|
||||
void HttpsUpstream::log_response_headers(DefaultMemchunks *buf) const {
|
||||
std::string nhdrs;
|
||||
if (log_config()->errorlog_tty) {
|
||||
nhdrs = http::colorizeHeaders(hdrs.c_str());
|
||||
hdrp = nhdrs.c_str();
|
||||
} else {
|
||||
hdrp = hdrs.c_str();
|
||||
for (auto chunk = buf->head; chunk; chunk = chunk->next) {
|
||||
nhdrs.append(chunk->pos, chunk->last);
|
||||
}
|
||||
ULOG(INFO, this) << "HTTP response headers\n" << hdrp;
|
||||
if (log_config()->errorlog_tty) {
|
||||
nhdrs = http::colorizeHeaders(nhdrs.c_str());
|
||||
}
|
||||
ULOG(INFO, this) << "HTTP response headers\n" << nhdrs;
|
||||
}
|
||||
|
||||
void HttpsUpstream::on_handler_delete() {
|
||||
@@ -1016,4 +1162,9 @@ fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HttpsUpstream::initiate_push(Downstream *downstream, const char *uri,
|
||||
size_t len) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace shrpx
|
||||
|
||||
@@ -74,9 +74,13 @@ public:
|
||||
|
||||
virtual void on_handler_delete();
|
||||
virtual int on_downstream_reset(bool no_retry);
|
||||
virtual int send_reply(Downstream *downstream, const uint8_t *body,
|
||||
size_t bodylen);
|
||||
virtual int initiate_push(Downstream *downstream, const char *uri,
|
||||
size_t len);
|
||||
|
||||
void reset_current_header_length();
|
||||
void log_response_headers(const std::string &hdrs) const;
|
||||
void log_response_headers(DefaultMemchunks *buf) const;
|
||||
|
||||
private:
|
||||
ClientHandler *handler_;
|
||||
|
||||
@@ -64,6 +64,7 @@ LogConfig::update_tstamp(const std::chrono::system_clock::time_point &now) {
|
||||
|
||||
time_local_str = util::format_common_log(now);
|
||||
time_iso8601_str = util::format_iso8601(now);
|
||||
time_http_str = util::format_http_date(now);
|
||||
}
|
||||
|
||||
} // namespace shrpx
|
||||
|
||||
@@ -35,6 +35,7 @@ struct LogConfig {
|
||||
std::chrono::system_clock::time_point time_str_updated_;
|
||||
std::string time_local_str;
|
||||
std::string time_iso8601_str;
|
||||
std::string time_http_str;
|
||||
int accesslog_fd;
|
||||
int errorlog_fd;
|
||||
// true if errorlog_fd is referring to a terminal.
|
||||
|
||||
207
src/shrpx_mruby.cc
Normal file
207
src/shrpx_mruby.cc
Normal file
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* nghttp2 - HTTP/2 C Library
|
||||
*
|
||||
* Copyright (c) 2015 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.
|
||||
*/
|
||||
#include "shrpx_mruby.h"
|
||||
|
||||
#include <mruby/compile.h>
|
||||
#include <mruby/string.h>
|
||||
|
||||
#include "shrpx_downstream.h"
|
||||
#include "shrpx_config.h"
|
||||
#include "shrpx_mruby_module.h"
|
||||
#include "shrpx_downstream_connection.h"
|
||||
#include "template.h"
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
namespace mruby {
|
||||
|
||||
MRubyContext::MRubyContext(mrb_state *mrb, RProc *on_request_proc,
|
||||
RProc *on_response_proc)
|
||||
: mrb_(mrb), on_request_proc_(on_request_proc),
|
||||
on_response_proc_(on_response_proc), running_(false) {}
|
||||
|
||||
MRubyContext::~MRubyContext() {
|
||||
if (mrb_) {
|
||||
mrb_close(mrb_);
|
||||
}
|
||||
}
|
||||
|
||||
int MRubyContext::run_request_proc(Downstream *downstream, RProc *proc,
|
||||
int phase) {
|
||||
if (!proc || running_) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
running_ = true;
|
||||
|
||||
MRubyAssocData data{downstream, phase};
|
||||
|
||||
mrb_->ud = &data;
|
||||
|
||||
int rv = 0;
|
||||
auto ai = mrb_gc_arena_save(mrb_);
|
||||
|
||||
auto res = mrb_run(mrb_, proc, mrb_top_self(mrb_));
|
||||
(void)res;
|
||||
|
||||
if (mrb_->exc) {
|
||||
// If response has been committed, ignore error
|
||||
if (downstream->get_response_state() != Downstream::MSG_COMPLETE) {
|
||||
rv = -1;
|
||||
}
|
||||
auto error =
|
||||
mrb_str_ptr(mrb_funcall(mrb_, mrb_obj_value(mrb_->exc), "inspect", 0));
|
||||
|
||||
LOG(ERROR) << "Exception caught while executing mruby code: "
|
||||
<< error->as.heap.ptr;
|
||||
mrb_->exc = 0;
|
||||
}
|
||||
|
||||
mrb_->ud = nullptr;
|
||||
|
||||
mrb_gc_arena_restore(mrb_, ai);
|
||||
|
||||
if (data.request_headers_dirty) {
|
||||
downstream->index_request_headers();
|
||||
}
|
||||
|
||||
if (data.response_headers_dirty) {
|
||||
downstream->index_response_headers();
|
||||
}
|
||||
|
||||
running_ = false;
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
int MRubyContext::run_on_request_proc(Downstream *downstream) {
|
||||
return run_request_proc(downstream, on_request_proc_, PHASE_REQUEST);
|
||||
}
|
||||
|
||||
int MRubyContext::run_on_response_proc(Downstream *downstream) {
|
||||
return run_request_proc(downstream, on_response_proc_, PHASE_RESPONSE);
|
||||
}
|
||||
|
||||
void MRubyContext::delete_downstream(Downstream *downstream) {
|
||||
if (!mrb_) {
|
||||
return;
|
||||
}
|
||||
delete_downstream_from_module(mrb_, downstream);
|
||||
}
|
||||
|
||||
// Based on
|
||||
// https://github.com/h2o/h2o/blob/master/lib/handler/mruby.c. It is
|
||||
// very hard to write these kind of code because mruby has almost no
|
||||
// documentation aobut compiling or generating code, at least at the
|
||||
// time of this writing.
|
||||
RProc *compile(mrb_state *mrb, const char *filename) {
|
||||
if (filename == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto infile = fopen(filename, "rb");
|
||||
if (infile == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
auto infile_d = defer(fclose, infile);
|
||||
|
||||
auto mrbc = mrbc_context_new(mrb);
|
||||
if (mrbc == nullptr) {
|
||||
LOG(ERROR) << "mrb_context_new failed";
|
||||
return nullptr;
|
||||
}
|
||||
auto mrbc_d = defer(mrbc_context_free, mrb, mrbc);
|
||||
|
||||
auto parser = mrb_parse_file(mrb, infile, nullptr);
|
||||
if (parser == nullptr) {
|
||||
LOG(ERROR) << "mrb_parse_nstring failed";
|
||||
return nullptr;
|
||||
}
|
||||
auto parser_d = defer(mrb_parser_free, parser);
|
||||
|
||||
if (parser->nerr != 0) {
|
||||
LOG(ERROR) << "mruby parser detected parse error";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto proc = mrb_generate_code(mrb, parser);
|
||||
if (proc == nullptr) {
|
||||
LOG(ERROR) << "mrb_generate_code failed";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return proc;
|
||||
}
|
||||
|
||||
std::unique_ptr<MRubyContext> create_mruby_context() {
|
||||
auto req_file = get_config()->request_phase_file.get();
|
||||
auto res_file = get_config()->response_phase_file.get();
|
||||
|
||||
if (!req_file && !res_file) {
|
||||
return make_unique<MRubyContext>(nullptr, nullptr, nullptr);
|
||||
}
|
||||
|
||||
auto mrb = mrb_open();
|
||||
if (mrb == nullptr) {
|
||||
LOG(ERROR) << "mrb_open failed";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
init_module(mrb);
|
||||
|
||||
auto req_proc = compile(mrb, req_file);
|
||||
|
||||
if (req_file && !req_proc) {
|
||||
LOG(ERROR) << "Could not compile mruby code " << req_file;
|
||||
mrb_close(mrb);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto res_proc = compile(mrb, res_file);
|
||||
|
||||
if (res_file && !res_proc) {
|
||||
LOG(ERROR) << "Could not compile mruby code " << res_file;
|
||||
mrb_close(mrb);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return make_unique<MRubyContext>(mrb, req_proc, res_proc);
|
||||
}
|
||||
|
||||
mrb_sym intern_ptr(mrb_state *mrb, void *ptr) {
|
||||
auto p = reinterpret_cast<uintptr_t>(ptr);
|
||||
|
||||
return mrb_intern(mrb, reinterpret_cast<const char *>(&p), sizeof(p));
|
||||
}
|
||||
|
||||
void check_phase(mrb_state *mrb, int phase, int phase_mask) {
|
||||
if ((phase & phase_mask) == 0) {
|
||||
mrb_raise(mrb, E_RUNTIME_ERROR, "operation was not allowed in this phase");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mruby
|
||||
|
||||
} // namespace shrpx
|
||||
88
src/shrpx_mruby.h
Normal file
88
src/shrpx_mruby.h
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* nghttp2 - HTTP/2 C Library
|
||||
*
|
||||
* Copyright (c) 2015 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.
|
||||
*/
|
||||
#ifndef SHRPX_MRUBY_H
|
||||
#define SHRPX_MRUBY_H
|
||||
|
||||
#include "shrpx.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <mruby.h>
|
||||
#include <mruby/proc.h>
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
namespace mruby {
|
||||
|
||||
class MRubyContext {
|
||||
public:
|
||||
MRubyContext(mrb_state *mrb, RProc *on_request_proc, RProc *on_response_proc);
|
||||
~MRubyContext();
|
||||
|
||||
int run_on_request_proc(Downstream *downstream);
|
||||
int run_on_response_proc(Downstream *downstream);
|
||||
|
||||
int run_request_proc(Downstream *downstream, RProc *proc, int phase);
|
||||
|
||||
void delete_downstream(Downstream *downstream);
|
||||
|
||||
private:
|
||||
mrb_state *mrb_;
|
||||
RProc *on_request_proc_;
|
||||
RProc *on_response_proc_;
|
||||
bool running_;
|
||||
};
|
||||
|
||||
enum {
|
||||
PHASE_NONE = 0,
|
||||
PHASE_REQUEST = 1,
|
||||
PHASE_RESPONSE = 1 << 1,
|
||||
};
|
||||
|
||||
struct MRubyAssocData {
|
||||
Downstream *downstream;
|
||||
int phase;
|
||||
bool request_headers_dirty;
|
||||
bool response_headers_dirty;
|
||||
};
|
||||
|
||||
RProc *compile(mrb_state *mrb, const char *filename);
|
||||
|
||||
std::unique_ptr<MRubyContext> create_mruby_context();
|
||||
|
||||
// Return interned |ptr|.
|
||||
mrb_sym intern_ptr(mrb_state *mrb, void *ptr);
|
||||
|
||||
// Checks that |phase| is set in |phase_mask|. If not set, raise
|
||||
// exception.
|
||||
void check_phase(mrb_state *mrb, int phase, int phase_mask);
|
||||
|
||||
} // namespace mruby
|
||||
|
||||
} // namespace shrpx
|
||||
|
||||
#endif // SHRPX_MRUBY_H
|
||||
128
src/shrpx_mruby_module.cc
Normal file
128
src/shrpx_mruby_module.cc
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* nghttp2 - HTTP/2 C Library
|
||||
*
|
||||
* Copyright (c) 2015 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.
|
||||
*/
|
||||
#include "shrpx_mruby_module.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <mruby/variable.h>
|
||||
#include <mruby/string.h>
|
||||
#include <mruby/hash.h>
|
||||
#include <mruby/array.h>
|
||||
|
||||
#include "shrpx_mruby.h"
|
||||
#include "shrpx_mruby_module_env.h"
|
||||
#include "shrpx_mruby_module_request.h"
|
||||
#include "shrpx_mruby_module_response.h"
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
namespace mruby {
|
||||
|
||||
namespace {
|
||||
mrb_value run(mrb_state *mrb, mrb_value self) {
|
||||
mrb_value b;
|
||||
mrb_get_args(mrb, "&", &b);
|
||||
|
||||
if (mrb_nil_p(b)) {
|
||||
return mrb_nil_value();
|
||||
}
|
||||
|
||||
auto module = mrb_module_get(mrb, "Nghttpx");
|
||||
|
||||
auto env_sym = mrb_intern_lit(mrb, "env");
|
||||
auto env = mrb_obj_iv_get(mrb, reinterpret_cast<RObject *>(module), env_sym);
|
||||
|
||||
if (mrb_nil_p(env)) {
|
||||
auto env_class = mrb_class_get_under(mrb, module, "Env");
|
||||
auto request_class = mrb_class_get_under(mrb, module, "Request");
|
||||
auto response_class = mrb_class_get_under(mrb, module, "Response");
|
||||
|
||||
env = mrb_obj_new(mrb, env_class, 0, nullptr);
|
||||
auto req = mrb_obj_new(mrb, request_class, 0, nullptr);
|
||||
auto resp = mrb_obj_new(mrb, response_class, 0, nullptr);
|
||||
|
||||
mrb_iv_set(mrb, env, mrb_intern_lit(mrb, "req"), req);
|
||||
mrb_iv_set(mrb, env, mrb_intern_lit(mrb, "resp"), resp);
|
||||
|
||||
mrb_obj_iv_set(mrb, reinterpret_cast<RObject *>(module), env_sym, env);
|
||||
}
|
||||
|
||||
std::array<mrb_value, 1> args{{env}};
|
||||
return mrb_yield_argv(mrb, b, args.size(), args.data());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void delete_downstream_from_module(mrb_state *mrb, Downstream *downstream) {
|
||||
auto module = mrb_module_get(mrb, "Nghttpx");
|
||||
auto env = mrb_obj_iv_get(mrb, reinterpret_cast<RObject *>(module),
|
||||
mrb_intern_lit(mrb, "env"));
|
||||
if (mrb_nil_p(env)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mrb_iv_remove(mrb, env, intern_ptr(mrb, downstream));
|
||||
}
|
||||
|
||||
void init_module(mrb_state *mrb) {
|
||||
auto module = mrb_define_module(mrb, "Nghttpx");
|
||||
|
||||
mrb_define_class_method(mrb, module, "run", run,
|
||||
MRB_ARGS_REQ(1) | MRB_ARGS_BLOCK());
|
||||
mrb_define_const(mrb, module, "REQUEST_PHASE",
|
||||
mrb_fixnum_value(PHASE_REQUEST));
|
||||
mrb_define_const(mrb, module, "RESPONSE_PHASE",
|
||||
mrb_fixnum_value(PHASE_RESPONSE));
|
||||
|
||||
init_env_class(mrb, module);
|
||||
init_request_class(mrb, module);
|
||||
init_response_class(mrb, module);
|
||||
}
|
||||
|
||||
mrb_value create_headers_hash(mrb_state *mrb, const Headers &headers) {
|
||||
auto hash = mrb_hash_new(mrb);
|
||||
|
||||
for (auto &hd : headers) {
|
||||
if (hd.name.empty() || hd.name[0] == ':') {
|
||||
continue;
|
||||
}
|
||||
auto ai = mrb_gc_arena_save(mrb);
|
||||
|
||||
auto key = mrb_str_new(mrb, hd.name.c_str(), hd.name.size());
|
||||
auto ary = mrb_hash_get(mrb, hash, key);
|
||||
if (mrb_nil_p(ary)) {
|
||||
ary = mrb_ary_new(mrb);
|
||||
mrb_hash_set(mrb, hash, key, ary);
|
||||
}
|
||||
mrb_ary_push(mrb, ary, mrb_str_new(mrb, hd.value.c_str(), hd.value.size()));
|
||||
|
||||
mrb_gc_arena_restore(mrb, ai);
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
} // namespace mruby
|
||||
|
||||
} // namespace shrpx
|
||||
52
src/shrpx_mruby_module.h
Normal file
52
src/shrpx_mruby_module.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* nghttp2 - HTTP/2 C Library
|
||||
*
|
||||
* Copyright (c) 2015 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.
|
||||
*/
|
||||
#ifndef SHRPX_MRUBY_MODULE_H
|
||||
#define SHRPX_MRUBY_MODULE_H
|
||||
|
||||
#include "shrpx.h"
|
||||
|
||||
#include <mruby.h>
|
||||
|
||||
#include "http2.h"
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
class Downstream;
|
||||
|
||||
namespace mruby {
|
||||
|
||||
void init_module(mrb_state *mrb);
|
||||
|
||||
void delete_downstream_from_module(mrb_state *mrb, Downstream *downstream);
|
||||
|
||||
mrb_value create_headers_hash(mrb_state *mrb, const Headers &headers);
|
||||
|
||||
} // namespace mruby
|
||||
|
||||
} // namespace shrpx
|
||||
|
||||
#endif // SHRPX_MRUBY_MODULE_H
|
||||
110
src/shrpx_mruby_module_env.cc
Normal file
110
src/shrpx_mruby_module_env.cc
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* nghttp2 - HTTP/2 C Library
|
||||
*
|
||||
* Copyright (c) 2015 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.
|
||||
*/
|
||||
#include "shrpx_mruby_module_env.h"
|
||||
|
||||
#include <mruby/variable.h>
|
||||
#include <mruby/string.h>
|
||||
#include <mruby/hash.h>
|
||||
|
||||
#include "shrpx_downstream.h"
|
||||
#include "shrpx_upstream.h"
|
||||
#include "shrpx_client_handler.h"
|
||||
#include "shrpx_mruby.h"
|
||||
#include "shrpx_mruby_module.h"
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
namespace mruby {
|
||||
|
||||
namespace {
|
||||
mrb_value env_init(mrb_state *mrb, mrb_value self) { return self; }
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value env_get_req(mrb_state *mrb, mrb_value self) {
|
||||
return mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "req"));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value env_get_resp(mrb_state *mrb, mrb_value self) {
|
||||
return mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "resp"));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value env_get_ctx(mrb_state *mrb, mrb_value self) {
|
||||
auto data = reinterpret_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
|
||||
auto dsym = intern_ptr(mrb, downstream);
|
||||
|
||||
auto ctx = mrb_iv_get(mrb, self, dsym);
|
||||
if (mrb_nil_p(ctx)) {
|
||||
ctx = mrb_hash_new(mrb);
|
||||
mrb_iv_set(mrb, self, dsym, ctx);
|
||||
}
|
||||
|
||||
return ctx;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value env_get_phase(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
|
||||
return mrb_fixnum_value(data->phase);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value env_get_remote_addr(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
auto upstream = downstream->get_upstream();
|
||||
auto handler = upstream->get_client_handler();
|
||||
|
||||
auto &ipaddr = handler->get_ipaddr();
|
||||
|
||||
return mrb_str_new(mrb, ipaddr.c_str(), ipaddr.size());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void init_env_class(mrb_state *mrb, RClass *module) {
|
||||
auto env_class =
|
||||
mrb_define_class_under(mrb, module, "Env", mrb->object_class);
|
||||
|
||||
mrb_define_method(mrb, env_class, "initialize", env_init, MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, env_class, "req", env_get_req, MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, env_class, "resp", env_get_resp, MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, env_class, "ctx", env_get_ctx, MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, env_class, "phase", env_get_phase, MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, env_class, "remote_addr", env_get_remote_addr,
|
||||
MRB_ARGS_NONE());
|
||||
}
|
||||
|
||||
} // namespace mruby
|
||||
|
||||
} // namespace shrpx
|
||||
44
src/shrpx_mruby_module_env.h
Normal file
44
src/shrpx_mruby_module_env.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* nghttp2 - HTTP/2 C Library
|
||||
*
|
||||
* Copyright (c) 2015 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.
|
||||
*/
|
||||
#ifndef SHRPX_MRUBY_MODULE_ENV_H
|
||||
#define SHRPX_MRUBY_MODULE_ENV_H
|
||||
|
||||
#include "shrpx.h"
|
||||
|
||||
#include <mruby.h>
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
namespace mruby {
|
||||
|
||||
void init_env_class(mrb_state *mrb, RClass *module);
|
||||
|
||||
} // namespace mruby
|
||||
|
||||
} // namespace shrpx
|
||||
|
||||
#endif // SHRPX_MRUBY_MODULE_ENV_H
|
||||
326
src/shrpx_mruby_module_request.cc
Normal file
326
src/shrpx_mruby_module_request.cc
Normal file
@@ -0,0 +1,326 @@
|
||||
/*
|
||||
* nghttp2 - HTTP/2 C Library
|
||||
*
|
||||
* Copyright (c) 2015 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.
|
||||
*/
|
||||
#include "shrpx_mruby_module_request.h"
|
||||
|
||||
#include <mruby/variable.h>
|
||||
#include <mruby/string.h>
|
||||
#include <mruby/hash.h>
|
||||
#include <mruby/array.h>
|
||||
|
||||
#include "shrpx_downstream.h"
|
||||
#include "shrpx_upstream.h"
|
||||
#include "shrpx_client_handler.h"
|
||||
#include "shrpx_mruby.h"
|
||||
#include "shrpx_mruby_module.h"
|
||||
#include "util.h"
|
||||
#include "http2.h"
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
namespace mruby {
|
||||
|
||||
namespace {
|
||||
mrb_value request_init(mrb_state *mrb, mrb_value self) { return self; }
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value request_get_http_version_major(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
return mrb_fixnum_value(downstream->get_request_major());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value request_get_http_version_minor(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
return mrb_fixnum_value(downstream->get_request_minor());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value request_get_method(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
auto method = http2::to_method_string(downstream->get_request_method());
|
||||
|
||||
return mrb_str_new_cstr(mrb, method);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value request_set_method(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
|
||||
check_phase(mrb, data->phase, PHASE_REQUEST);
|
||||
|
||||
const char *method;
|
||||
mrb_int n;
|
||||
mrb_get_args(mrb, "s", &method, &n);
|
||||
if (n == 0) {
|
||||
mrb_raise(mrb, E_RUNTIME_ERROR, "method must not be empty string");
|
||||
}
|
||||
auto token =
|
||||
http2::lookup_method_token(reinterpret_cast<const uint8_t *>(method), n);
|
||||
if (token == -1) {
|
||||
mrb_raise(mrb, E_RUNTIME_ERROR, "method not supported");
|
||||
}
|
||||
|
||||
downstream->set_request_method(token);
|
||||
|
||||
return self;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value request_get_authority(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
auto &authority = downstream->get_request_http2_authority();
|
||||
|
||||
return mrb_str_new(mrb, authority.c_str(), authority.size());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value request_set_authority(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
|
||||
check_phase(mrb, data->phase, PHASE_REQUEST);
|
||||
|
||||
const char *authority;
|
||||
mrb_int n;
|
||||
mrb_get_args(mrb, "s", &authority, &n);
|
||||
if (n == 0) {
|
||||
mrb_raise(mrb, E_RUNTIME_ERROR, "authority must not be empty string");
|
||||
}
|
||||
|
||||
downstream->set_request_http2_authority(std::string(authority, n));
|
||||
|
||||
return self;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value request_get_scheme(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
auto &scheme = downstream->get_request_http2_scheme();
|
||||
|
||||
return mrb_str_new(mrb, scheme.c_str(), scheme.size());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value request_set_scheme(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
|
||||
check_phase(mrb, data->phase, PHASE_REQUEST);
|
||||
|
||||
const char *scheme;
|
||||
mrb_int n;
|
||||
mrb_get_args(mrb, "s", &scheme, &n);
|
||||
if (n == 0) {
|
||||
mrb_raise(mrb, E_RUNTIME_ERROR, "scheme must not be empty string");
|
||||
}
|
||||
|
||||
downstream->set_request_http2_scheme(std::string(scheme, n));
|
||||
|
||||
return self;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value request_get_path(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
auto &path = downstream->get_request_path();
|
||||
|
||||
return mrb_str_new(mrb, path.c_str(), path.size());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value request_set_path(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
|
||||
check_phase(mrb, data->phase, PHASE_REQUEST);
|
||||
|
||||
const char *path;
|
||||
mrb_int pathlen;
|
||||
mrb_get_args(mrb, "s", &path, &pathlen);
|
||||
|
||||
downstream->set_request_path(std::string(path, pathlen));
|
||||
|
||||
return self;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value request_get_headers(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
return create_headers_hash(mrb, downstream->get_request_headers());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value request_mod_header(mrb_state *mrb, mrb_value self, bool repl) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
|
||||
check_phase(mrb, data->phase, PHASE_REQUEST);
|
||||
|
||||
mrb_value key, values;
|
||||
mrb_get_args(mrb, "oo", &key, &values);
|
||||
|
||||
if (RSTRING_LEN(key) == 0) {
|
||||
mrb_raise(mrb, E_RUNTIME_ERROR, "empty key is not allowed");
|
||||
}
|
||||
|
||||
key = mrb_funcall(mrb, key, "downcase", 0);
|
||||
|
||||
if (repl) {
|
||||
size_t p = 0;
|
||||
auto &headers = downstream->get_request_headers();
|
||||
for (size_t i = 0; i < headers.size(); ++i) {
|
||||
auto &hd = headers[i];
|
||||
if (util::streq(std::begin(hd.name), hd.name.size(), RSTRING_PTR(key),
|
||||
RSTRING_LEN(key))) {
|
||||
continue;
|
||||
}
|
||||
if (i != p) {
|
||||
headers[p++] = std::move(hd);
|
||||
}
|
||||
}
|
||||
headers.resize(p);
|
||||
}
|
||||
|
||||
if (mrb_obj_is_instance_of(mrb, values, mrb->array_class)) {
|
||||
auto n = mrb_ary_len(mrb, values);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
auto value = mrb_ary_entry(values, i);
|
||||
downstream->add_request_header(
|
||||
std::string(RSTRING_PTR(key), RSTRING_LEN(key)),
|
||||
std::string(RSTRING_PTR(value), RSTRING_LEN(value)));
|
||||
}
|
||||
} else if (!mrb_nil_p(values)) {
|
||||
downstream->add_request_header(
|
||||
std::string(RSTRING_PTR(key), RSTRING_LEN(key)),
|
||||
std::string(RSTRING_PTR(values), RSTRING_LEN(values)));
|
||||
}
|
||||
|
||||
data->request_headers_dirty = true;
|
||||
|
||||
return mrb_nil_value();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value request_set_header(mrb_state *mrb, mrb_value self) {
|
||||
return request_mod_header(mrb, self, true);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value request_add_header(mrb_state *mrb, mrb_value self) {
|
||||
return request_mod_header(mrb, self, false);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value request_clear_headers(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
|
||||
check_phase(mrb, data->phase, PHASE_REQUEST);
|
||||
|
||||
downstream->clear_request_headers();
|
||||
|
||||
return mrb_nil_value();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value request_push(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
auto upstream = downstream->get_upstream();
|
||||
|
||||
const char *uri;
|
||||
mrb_int len;
|
||||
mrb_get_args(mrb, "s", &uri, &len);
|
||||
|
||||
upstream->initiate_push(downstream, uri, len);
|
||||
|
||||
return mrb_nil_value();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void init_request_class(mrb_state *mrb, RClass *module) {
|
||||
auto request_class =
|
||||
mrb_define_class_under(mrb, module, "Request", mrb->object_class);
|
||||
|
||||
mrb_define_method(mrb, request_class, "initialize", request_init,
|
||||
MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, request_class, "http_version_major",
|
||||
request_get_http_version_major, MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, request_class, "http_version_minor",
|
||||
request_get_http_version_minor, MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, request_class, "method", request_get_method,
|
||||
MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, request_class, "method=", request_set_method,
|
||||
MRB_ARGS_REQ(1));
|
||||
mrb_define_method(mrb, request_class, "authority", request_get_authority,
|
||||
MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, request_class, "authority=", request_set_authority,
|
||||
MRB_ARGS_REQ(1));
|
||||
mrb_define_method(mrb, request_class, "scheme", request_get_scheme,
|
||||
MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, request_class, "scheme=", request_set_scheme,
|
||||
MRB_ARGS_REQ(1));
|
||||
mrb_define_method(mrb, request_class, "path", request_get_path,
|
||||
MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, request_class, "path=", request_set_path,
|
||||
MRB_ARGS_REQ(1));
|
||||
mrb_define_method(mrb, request_class, "headers", request_get_headers,
|
||||
MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, request_class, "add_header", request_add_header,
|
||||
MRB_ARGS_REQ(2));
|
||||
mrb_define_method(mrb, request_class, "set_header", request_set_header,
|
||||
MRB_ARGS_REQ(2));
|
||||
mrb_define_method(mrb, request_class, "clear_headers", request_clear_headers,
|
||||
MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, request_class, "push", request_push, MRB_ARGS_REQ(1));
|
||||
}
|
||||
|
||||
} // namespace mruby
|
||||
|
||||
} // namespace shrpx
|
||||
44
src/shrpx_mruby_module_request.h
Normal file
44
src/shrpx_mruby_module_request.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* nghttp2 - HTTP/2 C Library
|
||||
*
|
||||
* Copyright (c) 2015 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.
|
||||
*/
|
||||
#ifndef SHRPX_MRUBY_MODULE_REQUEST_H
|
||||
#define SHRPX_MRUBY_MODULE_REQUEST_H
|
||||
|
||||
#include "shrpx.h"
|
||||
|
||||
#include <mruby.h>
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
namespace mruby {
|
||||
|
||||
void init_request_class(mrb_state *mrb, RClass *module);
|
||||
|
||||
} // namespace mruby
|
||||
|
||||
} // namespace shrpx
|
||||
|
||||
#endif // SHRPX_MRUBY_MODULE_REQUEST_H
|
||||
264
src/shrpx_mruby_module_response.cc
Normal file
264
src/shrpx_mruby_module_response.cc
Normal file
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* nghttp2 - HTTP/2 C Library
|
||||
*
|
||||
* Copyright (c) 2015 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.
|
||||
*/
|
||||
#include "shrpx_mruby_module_response.h"
|
||||
|
||||
#include <mruby/variable.h>
|
||||
#include <mruby/string.h>
|
||||
#include <mruby/hash.h>
|
||||
#include <mruby/array.h>
|
||||
|
||||
#include "shrpx_downstream.h"
|
||||
#include "shrpx_upstream.h"
|
||||
#include "shrpx_client_handler.h"
|
||||
#include "shrpx_mruby.h"
|
||||
#include "shrpx_mruby_module.h"
|
||||
#include "util.h"
|
||||
#include "http2.h"
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
namespace mruby {
|
||||
|
||||
namespace {
|
||||
mrb_value response_init(mrb_state *mrb, mrb_value self) { return self; }
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value response_get_http_version_major(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
return mrb_fixnum_value(downstream->get_response_major());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value response_get_http_version_minor(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
return mrb_fixnum_value(downstream->get_response_minor());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value response_get_status(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
|
||||
return mrb_fixnum_value(downstream->get_response_http_status());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value response_set_status(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
|
||||
mrb_int status;
|
||||
mrb_get_args(mrb, "i", &status);
|
||||
// We don't support 1xx status code for mruby scripting yet.
|
||||
if (status < 200 || status > 999) {
|
||||
mrb_raise(mrb, E_RUNTIME_ERROR,
|
||||
"invalid status; it should be [200, 999], inclusive");
|
||||
}
|
||||
|
||||
downstream->set_response_http_status(status);
|
||||
|
||||
return self;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value response_get_headers(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
return create_headers_hash(mrb, downstream->get_response_headers());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value response_mod_header(mrb_state *mrb, mrb_value self, bool repl) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
|
||||
mrb_value key, values;
|
||||
mrb_get_args(mrb, "oo", &key, &values);
|
||||
|
||||
if (RSTRING_LEN(key) == 0) {
|
||||
mrb_raise(mrb, E_RUNTIME_ERROR, "empty key is not allowed");
|
||||
}
|
||||
|
||||
key = mrb_funcall(mrb, key, "downcase", 0);
|
||||
|
||||
if (repl) {
|
||||
size_t p = 0;
|
||||
auto &headers = downstream->get_response_headers();
|
||||
for (size_t i = 0; i < headers.size(); ++i) {
|
||||
auto &hd = headers[i];
|
||||
if (util::streq(std::begin(hd.name), hd.name.size(), RSTRING_PTR(key),
|
||||
RSTRING_LEN(key))) {
|
||||
continue;
|
||||
}
|
||||
if (i != p) {
|
||||
headers[p++] = std::move(hd);
|
||||
}
|
||||
}
|
||||
headers.resize(p);
|
||||
}
|
||||
|
||||
if (mrb_obj_is_instance_of(mrb, values, mrb->array_class)) {
|
||||
auto n = mrb_ary_len(mrb, values);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
auto value = mrb_ary_entry(values, i);
|
||||
downstream->add_response_header(
|
||||
std::string(RSTRING_PTR(key), RSTRING_LEN(key)),
|
||||
std::string(RSTRING_PTR(value), RSTRING_LEN(value)));
|
||||
}
|
||||
} else if (!mrb_nil_p(values)) {
|
||||
downstream->add_response_header(
|
||||
std::string(RSTRING_PTR(key), RSTRING_LEN(key)),
|
||||
std::string(RSTRING_PTR(values), RSTRING_LEN(values)));
|
||||
}
|
||||
|
||||
data->response_headers_dirty = true;
|
||||
|
||||
return mrb_nil_value();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value response_set_header(mrb_state *mrb, mrb_value self) {
|
||||
return response_mod_header(mrb, self, true);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value response_add_header(mrb_state *mrb, mrb_value self) {
|
||||
return response_mod_header(mrb, self, false);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value response_clear_headers(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
|
||||
downstream->clear_response_headers();
|
||||
|
||||
return mrb_nil_value();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
mrb_value response_return(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
int rv;
|
||||
|
||||
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
|
||||
mrb_raise(mrb, E_RUNTIME_ERROR, "response has already been committed");
|
||||
}
|
||||
|
||||
const char *val;
|
||||
mrb_int vallen;
|
||||
mrb_get_args(mrb, "|s", &val, &vallen);
|
||||
|
||||
const uint8_t *body = nullptr;
|
||||
size_t bodylen = 0;
|
||||
|
||||
if (downstream->get_response_http_status() == 0) {
|
||||
downstream->set_response_http_status(200);
|
||||
}
|
||||
|
||||
if (data->response_headers_dirty) {
|
||||
downstream->index_response_headers();
|
||||
data->response_headers_dirty = false;
|
||||
}
|
||||
|
||||
if (downstream->expect_response_body() && vallen > 0) {
|
||||
body = reinterpret_cast<const uint8_t *>(val);
|
||||
bodylen = vallen;
|
||||
}
|
||||
|
||||
auto cl = downstream->get_response_header(http2::HD_CONTENT_LENGTH);
|
||||
if (cl) {
|
||||
cl->value = util::utos(bodylen);
|
||||
} else {
|
||||
downstream->add_response_header("content-length", util::utos(bodylen),
|
||||
http2::HD_CONTENT_LENGTH);
|
||||
}
|
||||
downstream->set_response_content_length(bodylen);
|
||||
|
||||
auto date = downstream->get_response_header(http2::HD_DATE);
|
||||
if (!date) {
|
||||
auto lgconf = log_config();
|
||||
lgconf->update_tstamp(std::chrono::system_clock::now());
|
||||
downstream->add_response_header("date", lgconf->time_http_str,
|
||||
http2::HD_DATE);
|
||||
}
|
||||
|
||||
auto upstream = downstream->get_upstream();
|
||||
|
||||
rv = upstream->send_reply(downstream, body, bodylen);
|
||||
if (rv != 0) {
|
||||
mrb_raise(mrb, E_RUNTIME_ERROR, "could not send response");
|
||||
}
|
||||
|
||||
auto handler = upstream->get_client_handler();
|
||||
|
||||
handler->signal_write();
|
||||
|
||||
return self;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void init_response_class(mrb_state *mrb, RClass *module) {
|
||||
auto response_class =
|
||||
mrb_define_class_under(mrb, module, "Response", mrb->object_class);
|
||||
|
||||
mrb_define_method(mrb, response_class, "initialize", response_init,
|
||||
MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, response_class, "http_version_major",
|
||||
response_get_http_version_major, MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, response_class, "http_version_minor",
|
||||
response_get_http_version_minor, MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, response_class, "status", response_get_status,
|
||||
MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, response_class, "status=", response_set_status,
|
||||
MRB_ARGS_REQ(1));
|
||||
mrb_define_method(mrb, response_class, "headers", response_get_headers,
|
||||
MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, response_class, "add_header", response_add_header,
|
||||
MRB_ARGS_REQ(2));
|
||||
mrb_define_method(mrb, response_class, "set_header", response_set_header,
|
||||
MRB_ARGS_REQ(2));
|
||||
mrb_define_method(mrb, response_class, "clear_headers",
|
||||
response_clear_headers, MRB_ARGS_NONE());
|
||||
mrb_define_method(mrb, response_class, "return", response_return,
|
||||
MRB_ARGS_OPT(1));
|
||||
}
|
||||
|
||||
} // namespace mruby
|
||||
|
||||
} // namespace shrpx
|
||||
44
src/shrpx_mruby_module_response.h
Normal file
44
src/shrpx_mruby_module_response.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* nghttp2 - HTTP/2 C Library
|
||||
*
|
||||
* Copyright (c) 2015 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.
|
||||
*/
|
||||
#ifndef SHRPX_MRUBY_MODULE_RESPONSE_H
|
||||
#define SHRPX_MRUBY_MODULE_RESPONSE_H
|
||||
|
||||
#include "shrpx.h"
|
||||
|
||||
#include <mruby.h>
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
namespace mruby {
|
||||
|
||||
void init_response_class(mrb_state *mrb, RClass *module);
|
||||
|
||||
} // namespace mruby
|
||||
|
||||
} // namespace shrpx
|
||||
|
||||
#endif // SHRPX_MRUBY_MODULE_RESPONSE_H
|
||||
@@ -36,6 +36,11 @@
|
||||
#include "shrpx_downstream_connection.h"
|
||||
#include "shrpx_config.h"
|
||||
#include "shrpx_http.h"
|
||||
#ifdef HAVE_MRUBY
|
||||
#include "shrpx_mruby.h"
|
||||
#endif // HAVE_MRUBY
|
||||
#include "shrpx_worker.h"
|
||||
#include "shrpx_http2_session.h"
|
||||
#include "http2.h"
|
||||
#include "util.h"
|
||||
#include "template.h"
|
||||
@@ -224,6 +229,8 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
|
||||
downstream->set_request_http2_authority(host->value);
|
||||
if (get_config()->http2_proxy || get_config()->client_proxy) {
|
||||
downstream->set_request_path(path->value);
|
||||
} else if (method_token == HTTP_OPTIONS && path->value == "*") {
|
||||
// Server-wide OPTIONS request. Path is empty.
|
||||
} else {
|
||||
downstream->set_request_path(http2::rewrite_clean_path(
|
||||
std::begin(path->value), std::end(path->value)));
|
||||
@@ -237,6 +244,21 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
|
||||
downstream->inspect_http2_request();
|
||||
|
||||
downstream->set_request_state(Downstream::HEADER_COMPLETE);
|
||||
|
||||
#ifdef HAVE_MRUBY
|
||||
auto handler = upstream->get_client_handler();
|
||||
auto worker = handler->get_worker();
|
||||
auto mruby_ctx = worker->get_mruby_context();
|
||||
|
||||
if (mruby_ctx->run_on_request_proc(downstream) != 0) {
|
||||
if (upstream->error_reply(downstream, 500) != 0) {
|
||||
ULOG(FATAL, upstream) << "error_reply failed";
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif // HAVE_MRUBY
|
||||
|
||||
if (frame->syn_stream.hd.flags & SPDYLAY_CTRL_FLAG_FIN) {
|
||||
if (!downstream->validate_request_bodylen()) {
|
||||
upstream->rst_stream(downstream, SPDYLAY_PROTOCOL_ERROR);
|
||||
@@ -247,6 +269,10 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
|
||||
downstream->set_request_state(Downstream::MSG_COMPLETE);
|
||||
}
|
||||
|
||||
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
|
||||
return;
|
||||
}
|
||||
|
||||
upstream->start_downstream(downstream);
|
||||
|
||||
break;
|
||||
@@ -574,6 +600,11 @@ int SpdyUpstream::downstream_read(DownstreamConnection *dconn) {
|
||||
if (rv == SHRPX_ERR_EOF) {
|
||||
return downstream_eof(dconn);
|
||||
}
|
||||
if (rv == SHRPX_ERR_DCONN_CANCELED) {
|
||||
downstream->pop_downstream_connection();
|
||||
handler_->signal_write();
|
||||
return 0;
|
||||
}
|
||||
if (rv != 0) {
|
||||
if (rv != SHRPX_ERR_NETWORK) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
@@ -773,6 +804,70 @@ ssize_t spdy_data_read_callback(spdylay_session *session, int32_t stream_id,
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int SpdyUpstream::send_reply(Downstream *downstream, const uint8_t *body,
|
||||
size_t bodylen) {
|
||||
int rv;
|
||||
|
||||
spdylay_data_provider data_prd, *data_prd_ptr = nullptr;
|
||||
if (bodylen) {
|
||||
data_prd.source.ptr = downstream;
|
||||
data_prd.read_callback = spdy_data_read_callback;
|
||||
data_prd_ptr = &data_prd;
|
||||
}
|
||||
|
||||
auto status_string =
|
||||
http2::get_status_string(downstream->get_response_http_status());
|
||||
|
||||
auto &headers = downstream->get_response_headers();
|
||||
|
||||
auto nva = std::vector<const char *>();
|
||||
// 3 for :status, :version and server
|
||||
nva.reserve(3 + headers.size());
|
||||
|
||||
nva.push_back(":status");
|
||||
nva.push_back(status_string.c_str());
|
||||
nva.push_back(":version");
|
||||
nva.push_back("HTTP/1.1");
|
||||
|
||||
for (auto &kv : headers) {
|
||||
if (kv.name.empty() || kv.name[0] == ':') {
|
||||
continue;
|
||||
}
|
||||
switch (kv.token) {
|
||||
case http2::HD_CONNECTION:
|
||||
case http2::HD_KEEP_ALIVE:
|
||||
case http2::HD_PROXY_CONNECTION:
|
||||
case http2::HD_TRANSFER_ENCODING:
|
||||
continue;
|
||||
}
|
||||
nva.push_back(kv.name.c_str());
|
||||
nva.push_back(kv.value.c_str());
|
||||
}
|
||||
|
||||
if (!downstream->get_response_header(http2::HD_SERVER)) {
|
||||
nva.push_back("server");
|
||||
nva.push_back(get_config()->server_name);
|
||||
}
|
||||
|
||||
nva.push_back(nullptr);
|
||||
|
||||
rv = spdylay_submit_response(session_, downstream->get_stream_id(),
|
||||
nva.data(), data_prd_ptr);
|
||||
if (rv < SPDYLAY_ERR_FATAL) {
|
||||
ULOG(FATAL, this) << "spdylay_submit_response() failed: "
|
||||
<< spdylay_strerror(rv);
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto buf = downstream->get_response_buf();
|
||||
|
||||
buf->append(body, bodylen);
|
||||
|
||||
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SpdyUpstream::error_reply(Downstream *downstream,
|
||||
unsigned int status_code) {
|
||||
int rv;
|
||||
@@ -786,6 +881,9 @@ int SpdyUpstream::error_reply(Downstream *downstream,
|
||||
data_prd.source.ptr = downstream;
|
||||
data_prd.read_callback = spdy_data_read_callback;
|
||||
|
||||
auto lgconf = log_config();
|
||||
lgconf->update_tstamp(std::chrono::system_clock::now());
|
||||
|
||||
std::string content_length = util::utos(html.size());
|
||||
std::string status_string = http2::get_status_string(status_code);
|
||||
const char *nv[] = {":status", status_string.c_str(),
|
||||
@@ -793,6 +891,7 @@ int SpdyUpstream::error_reply(Downstream *downstream,
|
||||
"content-type", "text/html; charset=UTF-8",
|
||||
"server", get_config()->server_name,
|
||||
"content-length", content_length.c_str(),
|
||||
"date", lgconf->time_http_str.c_str(),
|
||||
nullptr};
|
||||
|
||||
rv = spdylay_submit_response(session_, downstream->get_stream_id(), nv,
|
||||
@@ -845,6 +944,23 @@ int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef HAVE_MRUBY
|
||||
auto worker = handler_->get_worker();
|
||||
auto mruby_ctx = worker->get_mruby_context();
|
||||
|
||||
if (mruby_ctx->run_on_response_proc(downstream) != 0) {
|
||||
if (error_reply(downstream, 500) != 0) {
|
||||
return -1;
|
||||
}
|
||||
// Returning -1 will signal deletion of dconn.
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
|
||||
return -1;
|
||||
}
|
||||
#endif // HAVE_MRUBY
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DLOG(INFO, downstream) << "HTTP response header completed";
|
||||
}
|
||||
@@ -1092,4 +1208,9 @@ int SpdyUpstream::on_downstream_reset(bool no_retry) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SpdyUpstream::initiate_push(Downstream *downstream, const char *uri,
|
||||
size_t len) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace shrpx
|
||||
|
||||
@@ -74,6 +74,11 @@ public:
|
||||
virtual void on_handler_delete();
|
||||
virtual int on_downstream_reset(bool no_retry);
|
||||
|
||||
virtual int send_reply(Downstream *downstream, const uint8_t *body,
|
||||
size_t bodylen);
|
||||
virtual int initiate_push(Downstream *downstream, const char *uri,
|
||||
size_t len);
|
||||
|
||||
bool get_flow_control() const;
|
||||
|
||||
int consume(int32_t stream_id, size_t len);
|
||||
|
||||
@@ -62,6 +62,11 @@ public:
|
||||
virtual void pause_read(IOCtrlReason reason) = 0;
|
||||
virtual int resume_read(IOCtrlReason reason, Downstream *downstream,
|
||||
size_t consumed) = 0;
|
||||
virtual int send_reply(Downstream *downstream, const uint8_t *body,
|
||||
size_t bodylen) = 0;
|
||||
|
||||
virtual int initiate_push(Downstream *downstream, const char *uri,
|
||||
size_t len) = 0;
|
||||
};
|
||||
|
||||
} // namespace shrpx
|
||||
|
||||
@@ -37,6 +37,9 @@
|
||||
#include "shrpx_log_config.h"
|
||||
#include "shrpx_connect_blocker.h"
|
||||
#include "shrpx_memcached_dispatcher.h"
|
||||
#ifdef HAVE_MRUBY
|
||||
#include "shrpx_mruby.h"
|
||||
#endif // HAVE_MRUBY
|
||||
#include "util.h"
|
||||
#include "template.h"
|
||||
|
||||
@@ -121,6 +124,7 @@ void Worker::run_async() {
|
||||
fut_ = std::async(std::launch::async, [this] {
|
||||
(void)reopen_log_files();
|
||||
ev_run(loop_);
|
||||
delete log_config();
|
||||
});
|
||||
#endif // !NOTHREADS
|
||||
}
|
||||
@@ -263,4 +267,19 @@ MemcachedDispatcher *Worker::get_session_cache_memcached_dispatcher() {
|
||||
return session_cache_memcached_dispatcher_.get();
|
||||
}
|
||||
|
||||
#ifdef HAVE_MRUBY
|
||||
int Worker::create_mruby_context() {
|
||||
mruby_ctx_ = mruby::create_mruby_context();
|
||||
if (!mruby_ctx_) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
mruby::MRubyContext *Worker::get_mruby_context() const {
|
||||
return mruby_ctx_.get();
|
||||
}
|
||||
#endif // HAVE_MRUBY
|
||||
|
||||
} // namespace shrpx
|
||||
|
||||
@@ -51,6 +51,14 @@ class Http2Session;
|
||||
class ConnectBlocker;
|
||||
class MemcachedDispatcher;
|
||||
|
||||
#ifdef HAVE_MRUBY
|
||||
namespace mruby {
|
||||
|
||||
class MRubyContext;
|
||||
|
||||
} // namespace mruby
|
||||
#endif // HAVE_MRUBY
|
||||
|
||||
namespace ssl {
|
||||
class CertLookupTree;
|
||||
} // namespace ssl
|
||||
@@ -124,6 +132,12 @@ public:
|
||||
|
||||
MemcachedDispatcher *get_session_cache_memcached_dispatcher();
|
||||
|
||||
#ifdef HAVE_MRUBY
|
||||
int create_mruby_context();
|
||||
|
||||
mruby::MRubyContext *get_mruby_context() const;
|
||||
#endif // HAVE_MRUBY
|
||||
|
||||
private:
|
||||
#ifndef NOTHREADS
|
||||
std::future<void> fut_;
|
||||
@@ -137,6 +151,9 @@ private:
|
||||
WorkerStat worker_stat_;
|
||||
std::vector<DownstreamGroup> dgrps_;
|
||||
std::unique_ptr<MemcachedDispatcher> session_cache_memcached_dispatcher_;
|
||||
#ifdef HAVE_MRUBY
|
||||
std::unique_ptr<mruby::MRubyContext> mruby_ctx_;
|
||||
#endif // HAVE_MRUBY
|
||||
struct ev_loop *loop_;
|
||||
|
||||
// Following fields are shared across threads if
|
||||
|
||||
29
src/util.cc
29
src/util.cc
@@ -66,16 +66,6 @@ const char DEFAULT_STRIP_CHARSET[] = "\r\n\t ";
|
||||
|
||||
const char UPPER_XDIGITS[] = "0123456789ABCDEF";
|
||||
|
||||
bool isAlpha(const char c) {
|
||||
return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
|
||||
}
|
||||
|
||||
bool isDigit(const char c) { return '0' <= c && c <= '9'; }
|
||||
|
||||
bool isHexDigit(const char c) {
|
||||
return isDigit(c) || ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f');
|
||||
}
|
||||
|
||||
bool inRFC3986UnreservedChars(const char c) {
|
||||
static const char unreserved[] = {'-', '.', '_', '~'};
|
||||
return isAlpha(c) || isDigit(c) ||
|
||||
@@ -636,15 +626,16 @@ void write_uri_field(std::ostream &o, const char *uri, const http_parser_url &u,
|
||||
}
|
||||
|
||||
bool numeric_host(const char *hostname) {
|
||||
struct addrinfo *res;
|
||||
struct addrinfo hints {};
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_flags = AI_NUMERICHOST;
|
||||
if (getaddrinfo(hostname, nullptr, &hints, &res)) {
|
||||
return false;
|
||||
}
|
||||
freeaddrinfo(res);
|
||||
return true;
|
||||
return numeric_host(hostname, AF_INET) || numeric_host(hostname, AF_INET6);
|
||||
}
|
||||
|
||||
bool numeric_host(const char *hostname, int family) {
|
||||
int rv;
|
||||
std::array<uint8_t, sizeof(struct in6_addr)> dst;
|
||||
|
||||
rv = inet_pton(family, hostname, dst.data());
|
||||
|
||||
return rv == 1;
|
||||
}
|
||||
|
||||
std::string numeric_name(const struct sockaddr *sa, socklen_t salen) {
|
||||
|
||||
19
src/util.h
19
src/util.h
@@ -158,11 +158,15 @@ std::string joinPath(InputIterator first, InputIterator last) {
|
||||
return strjoin(elements.begin(), elements.end(), "/");
|
||||
}
|
||||
|
||||
bool isAlpha(const char c);
|
||||
inline bool isAlpha(const char c) {
|
||||
return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
|
||||
}
|
||||
|
||||
bool isDigit(const char c);
|
||||
inline bool isDigit(const char c) { return '0' <= c && c <= '9'; }
|
||||
|
||||
bool isHexDigit(const char c);
|
||||
inline bool isHexDigit(const char c) {
|
||||
return isDigit(c) || ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f');
|
||||
}
|
||||
|
||||
bool inRFC3986UnreservedChars(const char c);
|
||||
|
||||
@@ -512,6 +516,8 @@ void write_uri_field(std::ostream &o, const char *uri, const http_parser_url &u,
|
||||
|
||||
bool numeric_host(const char *hostname);
|
||||
|
||||
bool numeric_host(const char *hostname, int family);
|
||||
|
||||
// Returns numeric address string of |addr|. If getnameinfo() is
|
||||
// failed, "unknown" is returned.
|
||||
std::string numeric_name(const struct sockaddr *sa, socklen_t salen);
|
||||
@@ -588,6 +594,13 @@ template <typename T> std::string format_iso8601(const T &tp) {
|
||||
return iso8601_date(t.count());
|
||||
}
|
||||
|
||||
// Returns given time |tp| in HTTP date format.
|
||||
template <typename T> std::string format_http_date(const T &tp) {
|
||||
auto t =
|
||||
std::chrono::duration_cast<std::chrono::seconds>(tp.time_since_epoch());
|
||||
return http_date(t.count());
|
||||
}
|
||||
|
||||
// Return the system precision of the template parameter |Clock| as
|
||||
// a nanosecond value of type |Rep|
|
||||
template <typename Clock, typename Rep> Rep clock_precision() {
|
||||
|
||||
21
third-party/Makefile.am
vendored
21
third-party/Makefile.am
vendored
@@ -30,5 +30,26 @@ libhttp_parser_la_SOURCES = \
|
||||
http-parser/http_parser.c \
|
||||
http-parser/http_parser.h
|
||||
|
||||
if HAVE_MRUBY
|
||||
|
||||
EXTRA_DIST = build_config.rb mruby/*
|
||||
|
||||
.PHONY: all-local clean mruby
|
||||
|
||||
mruby:
|
||||
MRUBY_CONFIG="${srcdir}/build_config.rb" \
|
||||
BUILD_DIR="${abs_builddir}/mruby/build" \
|
||||
INSTALL_DIR="${abs_builddir}/mruby/build/install/bin" \
|
||||
CC="${CC}" CXX="${CXX}" LD="${LD}" \
|
||||
CFLAGS="${CPPFLAGS} ${CFLAGS}" CXXFLAGS="${CPPFLAGS} ${CXXFLAGS}" \
|
||||
LDFLAGS="${LDFLAGS}" \
|
||||
"${srcdir}/mruby/minirake" -f "${srcdir}/mruby/Rakefile"
|
||||
|
||||
all-local: mruby
|
||||
|
||||
clean-local:
|
||||
-rm -rf "${abs_builddir}/mruby/build"
|
||||
|
||||
endif # HAVE_MRUBY
|
||||
|
||||
endif # ENABLE_THIRD_PARTY
|
||||
|
||||
14
third-party/build_config.rb
vendored
Normal file
14
third-party/build_config.rb
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
MRuby::Build.new do |conf|
|
||||
# TODO use same compilers configured in configure script
|
||||
toolchain :clang
|
||||
|
||||
# C++ project needs this. Without this, mruby exception does not
|
||||
# properly destory C++ object allocated on stack.
|
||||
conf.enable_cxx_abi
|
||||
|
||||
conf.build_dir = ENV['BUILD_DIR']
|
||||
|
||||
# include the default GEMs
|
||||
conf.gembox 'default'
|
||||
conf.gem :core => 'mruby-eval'
|
||||
end
|
||||
1
third-party/mruby
vendored
Submodule
1
third-party/mruby
vendored
Submodule
Submodule third-party/mruby added at eb9bec19dc
Reference in New Issue
Block a user