Merge branch 'nghttpx-mruby'
This commit is contained in:
commit
6d46249b7b
|
@ -6,6 +6,7 @@
|
||||||
*.lo
|
*.lo
|
||||||
*.m4
|
*.m4
|
||||||
*.o
|
*.o
|
||||||
|
*.pyc
|
||||||
.deps/
|
.deps/
|
||||||
.libs/
|
.libs/
|
||||||
INSTALL
|
INSTALL
|
||||||
|
@ -31,10 +32,10 @@ test-driver
|
||||||
# test logs generated by `make check`
|
# test logs generated by `make check`
|
||||||
*.log
|
*.log
|
||||||
*.trs
|
*.trs
|
||||||
|
|
||||||
lib/MSVC_obj/
|
lib/MSVC_obj/
|
||||||
_VC_ROOT/
|
_VC_ROOT/
|
||||||
.depend.MSVC
|
.depend.MSVC
|
||||||
*.pyd
|
*.pyd
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
python/nghttp2.c
|
python/nghttp2.c
|
||||||
|
|
|
@ -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
|
- autoreconf -i
|
||||||
- automake
|
- automake
|
||||||
- autoconf
|
- autoconf
|
||||||
- ./configure --enable-werror
|
- git submodule update --init
|
||||||
|
- ./configure --enable-werror --with-mruby
|
||||||
script:
|
script:
|
||||||
- make
|
- make
|
||||||
- make check
|
- make check
|
||||||
|
|
16
README.rst
16
README.rst
|
@ -113,6 +113,16 @@ 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:
|
spdylay is not packaged in Ubuntu, so you need to build it yourself:
|
||||||
http://tatsuhiro-t.github.io/spdylay/
|
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.
|
||||||
|
|
||||||
Building from git
|
Building from git
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
@ -127,6 +137,12 @@ used::
|
||||||
|
|
||||||
To compile the source code, gcc >= 4.8.3 or clang >= 3.4 is required.
|
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::
|
.. note::
|
||||||
|
|
||||||
Mac OS X users may need the ``--disable-threads`` configure option to
|
Mac OS X users may need the ``--disable-threads`` configure option to
|
||||||
|
|
19
configure.ac
19
configure.ac
|
@ -119,6 +119,11 @@ AC_ARG_WITH([spdylay],
|
||||||
[Use spdylay [default=check]])],
|
[Use spdylay [default=check]])],
|
||||||
[request_spdylay=$withval], [request_spdylay=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],
|
AC_ARG_WITH([cython],
|
||||||
[AS_HELP_STRING([--with-cython=PATH],
|
[AS_HELP_STRING([--with-cython=PATH],
|
||||||
[Use cython in given PATH])],
|
[Use cython in given PATH])],
|
||||||
|
@ -370,6 +375,19 @@ fi
|
||||||
|
|
||||||
AM_CONDITIONAL([HAVE_SPDYLAY], [ test "x${have_spdylay}" = "xyes" ])
|
AM_CONDITIONAL([HAVE_SPDYLAY], [ test "x${have_spdylay}" = "xyes" ])
|
||||||
|
|
||||||
|
# mruby (for src/nghttpx)
|
||||||
|
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
|
# Check Boost Asio library
|
||||||
have_asio_lib=no
|
have_asio_lib=no
|
||||||
|
|
||||||
|
@ -717,6 +735,7 @@ AC_MSG_NOTICE([summary of build options:
|
||||||
Libev: ${have_libev}
|
Libev: ${have_libev}
|
||||||
Libevent(SSL): ${have_libevent_openssl}
|
Libevent(SSL): ${have_libevent_openssl}
|
||||||
Spdylay: ${have_spdylay}
|
Spdylay: ${have_spdylay}
|
||||||
|
MRuby: ${have_mruby}
|
||||||
Jansson: ${have_jansson}
|
Jansson: ${have_jansson}
|
||||||
Jemalloc: ${have_jemalloc}
|
Jemalloc: ${have_jemalloc}
|
||||||
Zlib: ${have_zlib}
|
Zlib: ${have_zlib}
|
||||||
|
|
|
@ -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.
|
|
@ -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__)
|
||||||
|
|
|
@ -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)
|
|
@ -41,6 +41,8 @@ import sys, os
|
||||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
#sys.path.insert(0, os.path.abspath('.'))
|
#sys.path.insert(0, os.path.abspath('.'))
|
||||||
|
|
||||||
|
sys.path.append(os.path.abspath('_exts'))
|
||||||
|
|
||||||
# -- General configuration -----------------------------------------------------
|
# -- General configuration -----------------------------------------------------
|
||||||
|
|
||||||
# If your documentation needs a minimal Sphinx version, state it here.
|
# 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
|
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
# 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.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['@top_srcdir@/_templates']
|
templates_path = ['@top_srcdir@/_templates']
|
||||||
|
|
233
doc/nghttpx.h2r
233
doc/nghttpx.h2r
|
@ -153,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
|
automatically. To rotate key, one has to restart nghttpx (see
|
||||||
SIGNALS).
|
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
|
SEE ALSO
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
|
|
@ -98,6 +98,8 @@ OPTIONS = [
|
||||||
"tls-ticket-key-memcached-interval",
|
"tls-ticket-key-memcached-interval",
|
||||||
"tls-ticket-key-memcached-max-retry",
|
"tls-ticket-key-memcached-max-retry",
|
||||||
"tls-ticket-key-memcached-max-fail",
|
"tls-ticket-key-memcached-max-fail",
|
||||||
|
"request-phase-file",
|
||||||
|
"response-phase-file",
|
||||||
"conf",
|
"conf",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,10 @@ EXTRA_DIST = \
|
||||||
server.crt \
|
server.crt \
|
||||||
alt-server.key \
|
alt-server.key \
|
||||||
alt-server.crt \
|
alt-server.crt \
|
||||||
setenv
|
setenv \
|
||||||
|
req-set-header.rb \
|
||||||
|
resp-set-header.rb \
|
||||||
|
return.rb
|
||||||
|
|
||||||
itprep-local:
|
itprep-local:
|
||||||
go get -d -v github.com/bradfitz/http2
|
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
|
// TestH1H2ConnectFailure tests that server handles the situation that
|
||||||
// connection attempt to HTTP/2 backend failed.
|
// connection attempt to HTTP/2 backend failed.
|
||||||
func TestH1H2ConnectFailure(t *testing.T) {
|
func TestH1H2ConnectFailure(t *testing.T) {
|
||||||
|
@ -547,3 +661,73 @@ func TestH1H2NoVia(t *testing.T) {
|
||||||
t.Errorf("Via: %v; want %v", got, want)
|
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
|
// TestH2H1Upgrade tests HTTP Upgrade to HTTP/2
|
||||||
func TestH2H1Upgrade(t *testing.T) {
|
func TestH2H1Upgrade(t *testing.T) {
|
||||||
st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {})
|
st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {})
|
||||||
|
@ -875,3 +989,73 @@ func TestH2H2TLSXfp(t *testing.T) {
|
||||||
t.Errorf("res.status: %v; want %v", got, want)
|
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
|
// TestS3H2ConnectFailure tests that server handles the situation that
|
||||||
// connection attempt to HTTP/2 backend failed.
|
// connection attempt to HTTP/2 backend failed.
|
||||||
func TestS3H2ConnectFailure(t *testing.T) {
|
func TestS3H2ConnectFailure(t *testing.T) {
|
||||||
|
@ -250,3 +364,73 @@ func TestS3H2ConnectFailure(t *testing.T) {
|
||||||
t.Errorf("status: %v; want %v", got, want)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
Nghttpx.run do |env|
|
||||||
|
env.req.set_header "User-Agent", "mruby"
|
||||||
|
end
|
|
@ -0,0 +1,3 @@
|
||||||
|
Nghttpx.run do |env|
|
||||||
|
env.resp.set_header "Alpha", "bravo"
|
||||||
|
end
|
|
@ -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
|
|
@ -6,6 +6,7 @@ PREV_TAG=$2
|
||||||
git checkout refs/tags/$TAG
|
git checkout refs/tags/$TAG
|
||||||
git log --pretty=fuller --date=short refs/tags/$PREV_TAG..HEAD > ChangeLog
|
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 dist-bzip2 && make dist-gzip && make dist-xz || echo "error"
|
||||||
make distclean
|
make distclean
|
||||||
|
|
|
@ -130,12 +130,29 @@ if HAVE_SPDYLAY
|
||||||
NGHTTPX_SRCS += shrpx_spdy_upstream.cc shrpx_spdy_upstream.h
|
NGHTTPX_SRCS += shrpx_spdy_upstream.cc shrpx_spdy_upstream.h
|
||||||
endif # HAVE_SPDYLAY
|
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
|
noinst_LIBRARIES = libnghttpx.a
|
||||||
libnghttpx_a_SOURCES = ${NGHTTPX_SRCS}
|
libnghttpx_a_SOURCES = ${NGHTTPX_SRCS}
|
||||||
|
libnghttpx_a_CPPFLAGS = ${AM_CPPFLAGS}
|
||||||
|
|
||||||
nghttpx_SOURCES = shrpx.cc shrpx.h
|
nghttpx_SOURCES = shrpx.cc shrpx.h
|
||||||
|
nghttpx_CPPFLAGS = ${libnghttpx_a_CPPFLAGS}
|
||||||
nghttpx_LDADD = libnghttpx.a ${LDADD}
|
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
|
if HAVE_CUNIT
|
||||||
check_PROGRAMS += nghttpx-unittest
|
check_PROGRAMS += nghttpx-unittest
|
||||||
nghttpx_unittest_SOURCES = shrpx-unittest.cc \
|
nghttpx_unittest_SOURCES = shrpx-unittest.cc \
|
||||||
|
@ -148,10 +165,17 @@ nghttpx_unittest_SOURCES = shrpx-unittest.cc \
|
||||||
nghttp2_gzip.c nghttp2_gzip.h \
|
nghttp2_gzip.c nghttp2_gzip.h \
|
||||||
buffer_test.cc buffer_test.h \
|
buffer_test.cc buffer_test.h \
|
||||||
memchunk_test.cc memchunk_test.h
|
memchunk_test.cc memchunk_test.h
|
||||||
nghttpx_unittest_CPPFLAGS = ${AM_CPPFLAGS}\
|
nghttpx_unittest_CPPFLAGS = ${AM_CPPFLAGS} \
|
||||||
-DNGHTTP2_TESTS_DIR=\"$(top_srcdir)/tests\"
|
-DNGHTTP2_TESTS_DIR=\"$(top_srcdir)/tests\"
|
||||||
nghttpx_unittest_LDADD = libnghttpx.a ${LDADD} @CUNIT_LIBS@ @TESTLDADD@
|
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
|
TESTS += nghttpx-unittest
|
||||||
endif # HAVE_CUNIT
|
endif # HAVE_CUNIT
|
||||||
|
|
||||||
|
|
94
src/http2.cc
94
src/http2.cc
|
@ -664,6 +664,15 @@ const Headers::value_type *get_header(const HeaderIndex &hdidx, int16_t token,
|
||||||
return &nva[i];
|
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 {
|
namespace {
|
||||||
template <typename InputIt> InputIt skip_lws(InputIt first, InputIt last) {
|
template <typename InputIt> InputIt skip_lws(InputIt first, InputIt last) {
|
||||||
for (; first != last; ++first) {
|
for (; first != last; ++first) {
|
||||||
|
@ -1299,6 +1308,91 @@ const char *to_method_string(int method_token) {
|
||||||
return http_method_str(static_cast<http_method>(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 http2
|
||||||
|
|
||||||
} // namespace nghttp2
|
} // namespace nghttp2
|
||||||
|
|
18
src/http2.h
18
src/http2.h
|
@ -262,6 +262,9 @@ bool http2_mandatory_request_headers_presence(const HeaderIndex &hdidx);
|
||||||
const Headers::value_type *get_header(const HeaderIndex &hdidx, int16_t token,
|
const Headers::value_type *get_header(const HeaderIndex &hdidx, int16_t token,
|
||||||
const Headers &nva);
|
const Headers &nva);
|
||||||
|
|
||||||
|
Headers::value_type *get_header(const HeaderIndex &hdidx, int16_t token,
|
||||||
|
Headers &nva);
|
||||||
|
|
||||||
struct LinkHeader {
|
struct LinkHeader {
|
||||||
// The region of URI is [uri.first, uri.second).
|
// The region of URI is [uri.first, uri.second).
|
||||||
std::pair<const char *, const char *> uri;
|
std::pair<const char *, const char *> uri;
|
||||||
|
@ -349,6 +352,21 @@ std::string rewrite_clean_path(InputIt first, InputIt last) {
|
||||||
return path;
|
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 http2
|
||||||
|
|
||||||
} // namespace nghttp2
|
} // namespace nghttp2
|
||||||
|
|
|
@ -880,4 +880,104 @@ void test_http2_rewrite_clean_path(void) {
|
||||||
CU_ASSERT(src == http2::rewrite_clean_path(std::begin(src), std::end(src)));
|
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
|
} // namespace shrpx
|
||||||
|
|
|
@ -47,6 +47,8 @@ void test_http2_parse_link_header(void);
|
||||||
void test_http2_path_join(void);
|
void test_http2_path_join(void);
|
||||||
void test_http2_normalize_path(void);
|
void test_http2_normalize_path(void);
|
||||||
void test_http2_rewrite_clean_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
|
} // namespace shrpx
|
||||||
|
|
||||||
|
|
|
@ -100,6 +100,10 @@ int main(int argc, char *argv[]) {
|
||||||
shrpx::test_http2_normalize_path) ||
|
shrpx::test_http2_normalize_path) ||
|
||||||
!CU_add_test(pSuite, "http2_rewrite_clean_path",
|
!CU_add_test(pSuite, "http2_rewrite_clean_path",
|
||||||
shrpx::test_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",
|
!CU_add_test(pSuite, "downstream_index_request_headers",
|
||||||
shrpx::test_downstream_index_request_headers) ||
|
shrpx::test_downstream_index_request_headers) ||
|
||||||
!CU_add_test(pSuite, "downstream_index_response_headers",
|
!CU_add_test(pSuite, "downstream_index_response_headers",
|
||||||
|
|
31
src/shrpx.cc
31
src/shrpx.cc
|
@ -926,9 +926,13 @@ int event_loop() {
|
||||||
#endif // !NOTHREADS
|
#endif // !NOTHREADS
|
||||||
|
|
||||||
if (get_config()->num_worker == 1) {
|
if (get_config()->num_worker == 1) {
|
||||||
conn_handler->create_single_worker();
|
rv = conn_handler->create_single_worker();
|
||||||
} else {
|
} 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
|
#ifndef NOTHREADS
|
||||||
|
@ -1726,6 +1730,16 @@ Process:
|
||||||
Run this program as <USER>. This option is intended to
|
Run this program as <USER>. This option is intended to
|
||||||
be used to drop root privileges.
|
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:
|
Misc:
|
||||||
--conf=<PATH>
|
--conf=<PATH>
|
||||||
Load configuration from <PATH>.
|
Load configuration from <PATH>.
|
||||||
|
@ -1899,6 +1913,8 @@ int main(int argc, char **argv) {
|
||||||
89},
|
89},
|
||||||
{SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL, required_argument, &flag,
|
{SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL, required_argument, &flag,
|
||||||
90},
|
90},
|
||||||
|
{SHRPX_OPT_REQUEST_PHASE_FILE, required_argument, &flag, 91},
|
||||||
|
{SHRPX_OPT_RESPONSE_PHASE_FILE, required_argument, &flag, 92},
|
||||||
{nullptr, 0, nullptr, 0}};
|
{nullptr, 0, nullptr, 0}};
|
||||||
|
|
||||||
int option_index = 0;
|
int option_index = 0;
|
||||||
|
@ -2296,6 +2312,13 @@ int main(int argc, char **argv) {
|
||||||
cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL,
|
cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL,
|
||||||
optarg);
|
optarg);
|
||||||
break;
|
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);
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -2613,7 +2636,9 @@ int main(int argc, char **argv) {
|
||||||
act.sa_handler = SIG_IGN;
|
act.sa_handler = SIG_IGN;
|
||||||
sigaction(SIGPIPE, &act, nullptr);
|
sigaction(SIGPIPE, &act, nullptr);
|
||||||
|
|
||||||
event_loop();
|
if (event_loop() != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
LOG(NOTICE) << "Shutdown momentarily";
|
LOG(NOTICE) << "Shutdown momentarily";
|
||||||
|
|
||||||
|
|
|
@ -739,41 +739,23 @@ namespace {
|
||||||
// HttpDownstreamConnection::push_request_headers(), but vastly
|
// HttpDownstreamConnection::push_request_headers(), but vastly
|
||||||
// simplified since we only care about absolute URI.
|
// simplified since we only care about absolute URI.
|
||||||
std::string construct_absolute_request_uri(Downstream *downstream) {
|
std::string construct_absolute_request_uri(Downstream *downstream) {
|
||||||
const char *authority = nullptr, *host = nullptr;
|
auto &authority = downstream->get_request_http2_authority();
|
||||||
if (!downstream->get_request_http2_authority().empty()) {
|
if (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) {
|
|
||||||
return downstream->get_request_path();
|
return downstream->get_request_path();
|
||||||
}
|
}
|
||||||
std::string uri;
|
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.,
|
// We may have to log the request which lacks scheme (e.g.,
|
||||||
// http/1.1 with origin form).
|
// http/1.1 with origin form).
|
||||||
uri += "http://";
|
uri += "http://";
|
||||||
} else {
|
} else {
|
||||||
uri += downstream->get_request_http2_scheme();
|
uri += scheme;
|
||||||
uri += "://";
|
uri += "://";
|
||||||
}
|
}
|
||||||
if (authority) {
|
uri += authority;
|
||||||
uri += authority;
|
uri += downstream->get_request_path();
|
||||||
} else {
|
|
||||||
uri += 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() != "*") {
|
|
||||||
uri += downstream->get_request_path();
|
|
||||||
}
|
|
||||||
return uri;
|
return uri;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -787,12 +769,15 @@ void ClientHandler::write_accesslog(Downstream *downstream) {
|
||||||
downstream, ipaddr_.c_str(),
|
downstream, ipaddr_.c_str(),
|
||||||
http2::to_method_string(downstream->get_request_method()),
|
http2::to_method_string(downstream->get_request_method()),
|
||||||
|
|
||||||
(downstream->get_request_method() != HTTP_CONNECT &&
|
downstream->get_request_method() == HTTP_CONNECT
|
||||||
(get_config()->http2_proxy || get_config()->client_proxy))
|
? downstream->get_request_http2_authority().c_str()
|
||||||
? construct_absolute_request_uri(downstream).c_str()
|
: (get_config()->http2_proxy || get_config()->client_proxy)
|
||||||
: downstream->get_request_path().empty()
|
? construct_absolute_request_uri(downstream).c_str()
|
||||||
? downstream->get_request_http2_authority().c_str()
|
: downstream->get_request_path().empty()
|
||||||
: downstream->get_request_path().c_str(),
|
? downstream->get_request_method() == HTTP_OPTIONS
|
||||||
|
? "*"
|
||||||
|
: "-"
|
||||||
|
: downstream->get_request_path().c_str(),
|
||||||
|
|
||||||
alpn_.c_str(),
|
alpn_.c_str(),
|
||||||
nghttp2::ssl::get_tls_session_info(&tls_info, conn_.tls.ssl),
|
nghttp2::ssl::get_tls_session_info(&tls_info, conn_.tls.ssl),
|
||||||
|
|
|
@ -696,6 +696,8 @@ enum {
|
||||||
SHRPX_OPTID_PRIVATE_KEY_PASSWD_FILE,
|
SHRPX_OPTID_PRIVATE_KEY_PASSWD_FILE,
|
||||||
SHRPX_OPTID_READ_BURST,
|
SHRPX_OPTID_READ_BURST,
|
||||||
SHRPX_OPTID_READ_RATE,
|
SHRPX_OPTID_READ_RATE,
|
||||||
|
SHRPX_OPTID_REQUEST_PHASE_FILE,
|
||||||
|
SHRPX_OPTID_RESPONSE_PHASE_FILE,
|
||||||
SHRPX_OPTID_RLIMIT_NOFILE,
|
SHRPX_OPTID_RLIMIT_NOFILE,
|
||||||
SHRPX_OPTID_STREAM_READ_TIMEOUT,
|
SHRPX_OPTID_STREAM_READ_TIMEOUT,
|
||||||
SHRPX_OPTID_STREAM_WRITE_TIMEOUT,
|
SHRPX_OPTID_STREAM_WRITE_TIMEOUT,
|
||||||
|
@ -1017,6 +1019,11 @@ int option_lookup_token(const char *name, size_t namelen) {
|
||||||
break;
|
break;
|
||||||
case 18:
|
case 18:
|
||||||
switch (name[17]) {
|
switch (name[17]) {
|
||||||
|
case 'e':
|
||||||
|
if (util::strieq_l("request-phase-fil", name, 17)) {
|
||||||
|
return SHRPX_OPTID_REQUEST_PHASE_FILE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'r':
|
case 'r':
|
||||||
if (util::strieq_l("add-request-heade", name, 17)) {
|
if (util::strieq_l("add-request-heade", name, 17)) {
|
||||||
return SHRPX_OPTID_ADD_REQUEST_HEADER;
|
return SHRPX_OPTID_ADD_REQUEST_HEADER;
|
||||||
|
@ -1035,6 +1042,9 @@ int option_lookup_token(const char *name, size_t namelen) {
|
||||||
if (util::strieq_l("no-location-rewrit", name, 18)) {
|
if (util::strieq_l("no-location-rewrit", name, 18)) {
|
||||||
return SHRPX_OPTID_NO_LOCATION_REWRITE;
|
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)) {
|
if (util::strieq_l("tls-ticket-key-fil", name, 18)) {
|
||||||
return SHRPX_OPTID_TLS_TICKET_KEY_FILE;
|
return SHRPX_OPTID_TLS_TICKET_KEY_FILE;
|
||||||
}
|
}
|
||||||
|
@ -1938,6 +1948,14 @@ int parse_config(const char *opt, const char *optarg,
|
||||||
case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL:
|
case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL:
|
||||||
return parse_uint(&mod_config()->tls_ticket_key_memcached_max_fail, opt,
|
return parse_uint(&mod_config()->tls_ticket_key_memcached_max_fail, opt,
|
||||||
optarg);
|
optarg);
|
||||||
|
case SHRPX_OPTID_REQUEST_PHASE_FILE:
|
||||||
|
mod_config()->request_phase_file = strcopy(optarg);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
case SHRPX_OPTID_RESPONSE_PHASE_FILE:
|
||||||
|
mod_config()->response_phase_file = strcopy(optarg);
|
||||||
|
|
||||||
|
return 0;
|
||||||
case SHRPX_OPTID_CONF:
|
case SHRPX_OPTID_CONF:
|
||||||
LOG(WARN) << "conf: ignored";
|
LOG(WARN) << "conf: ignored";
|
||||||
|
|
||||||
|
|
|
@ -183,6 +183,8 @@ constexpr char SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY[] =
|
||||||
"tls-ticket-key-memcached-max-retry";
|
"tls-ticket-key-memcached-max-retry";
|
||||||
constexpr char SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL[] =
|
constexpr char SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL[] =
|
||||||
"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";
|
||||||
|
|
||||||
union sockaddr_union {
|
union sockaddr_union {
|
||||||
sockaddr_storage storage;
|
sockaddr_storage storage;
|
||||||
|
@ -314,6 +316,8 @@ struct Config {
|
||||||
std::unique_ptr<char[]> user;
|
std::unique_ptr<char[]> user;
|
||||||
std::unique_ptr<char[]> session_cache_memcached_host;
|
std::unique_ptr<char[]> session_cache_memcached_host;
|
||||||
std::unique_ptr<char[]> tls_ticket_key_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_request_header;
|
||||||
FILE *http2_upstream_dump_response_header;
|
FILE *http2_upstream_dump_response_header;
|
||||||
nghttp2_session_callbacks *http2_upstream_callbacks;
|
nghttp2_session_callbacks *http2_upstream_callbacks;
|
||||||
|
|
|
@ -156,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 cert_tree = ssl::create_cert_lookup_tree();
|
||||||
auto sv_ssl_ctx = ssl::setup_server_ssl_context(all_ssl_ctx_, cert_tree);
|
auto sv_ssl_ctx = ssl::setup_server_ssl_context(all_ssl_ctx_, cert_tree);
|
||||||
auto cl_ssl_ctx = ssl::setup_client_ssl_context();
|
auto cl_ssl_ctx = ssl::setup_client_ssl_context();
|
||||||
|
@ -167,9 +167,16 @@ void ConnectionHandler::create_single_worker() {
|
||||||
|
|
||||||
single_worker_ = make_unique<Worker>(loop_, sv_ssl_ctx, cl_ssl_ctx, cert_tree,
|
single_worker_ = make_unique<Worker>(loop_, sv_ssl_ctx, cl_ssl_ctx, cert_tree,
|
||||||
ticket_keys_);
|
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
|
#ifndef NOTHREADS
|
||||||
assert(workers_.size() == 0);
|
assert(workers_.size() == 0);
|
||||||
|
|
||||||
|
@ -186,7 +193,12 @@ void ConnectionHandler::create_worker_thread(size_t num) {
|
||||||
|
|
||||||
auto worker = make_unique<Worker>(loop, sv_ssl_ctx, cl_ssl_ctx, cert_tree,
|
auto worker = make_unique<Worker>(loop, sv_ssl_ctx, cl_ssl_ctx, cert_tree,
|
||||||
ticket_keys_);
|
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));
|
workers_.push_back(std::move(worker));
|
||||||
worker_loops_.push_back(loop);
|
worker_loops_.push_back(loop);
|
||||||
|
|
||||||
|
@ -194,7 +206,13 @@ void ConnectionHandler::create_worker_thread(size_t num) {
|
||||||
LLOG(INFO, this) << "Created thread #" << workers_.size() - 1;
|
LLOG(INFO, this) << "Created thread #" << workers_.size() - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto &worker : workers_) {
|
||||||
|
worker->run_async();
|
||||||
|
}
|
||||||
#endif // NOTHREADS
|
#endif // NOTHREADS
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConnectionHandler::join_worker() {
|
void ConnectionHandler::join_worker() {
|
||||||
|
|
|
@ -73,10 +73,10 @@ public:
|
||||||
~ConnectionHandler();
|
~ConnectionHandler();
|
||||||
int handle_connection(int fd, sockaddr *addr, int addrlen);
|
int handle_connection(int fd, sockaddr *addr, int addrlen);
|
||||||
// Creates Worker object for single threaded configuration.
|
// Creates Worker object for single threaded configuration.
|
||||||
void create_single_worker();
|
int create_single_worker();
|
||||||
// Creates |num| Worker objects for multi threaded configuration.
|
// Creates |num| Worker objects for multi threaded configuration.
|
||||||
// The |num| must be strictly more than 1.
|
// The |num| must be strictly more than 1.
|
||||||
void create_worker_thread(size_t num);
|
int create_worker_thread(size_t num);
|
||||||
void
|
void
|
||||||
set_ticket_keys_to_worker(const std::shared_ptr<TicketKeys> &ticket_keys);
|
set_ticket_keys_to_worker(const std::shared_ptr<TicketKeys> &ticket_keys);
|
||||||
void worker_reopen_log_files();
|
void worker_reopen_log_files();
|
||||||
|
|
|
@ -34,6 +34,11 @@
|
||||||
#include "shrpx_error.h"
|
#include "shrpx_error.h"
|
||||||
#include "shrpx_downstream_connection.h"
|
#include "shrpx_downstream_connection.h"
|
||||||
#include "shrpx_downstream_queue.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 "util.h"
|
||||||
#include "http2.h"
|
#include "http2.h"
|
||||||
|
|
||||||
|
@ -160,6 +165,14 @@ Downstream::~Downstream() {
|
||||||
ev_timer_stop(loop, &upstream_wtimer_);
|
ev_timer_stop(loop, &upstream_wtimer_);
|
||||||
ev_timer_stop(loop, &downstream_rtimer_);
|
ev_timer_stop(loop, &downstream_rtimer_);
|
||||||
ev_timer_stop(loop, &downstream_wtimer_);
|
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
|
// DownstreamConnection may refer to this object. Delete it now
|
||||||
|
@ -240,6 +253,8 @@ const Headers &Downstream::get_request_headers() const {
|
||||||
return request_headers_;
|
return request_headers_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Headers &Downstream::get_request_headers() { return request_headers_; }
|
||||||
|
|
||||||
void Downstream::assemble_request_cookie() {
|
void Downstream::assemble_request_cookie() {
|
||||||
std::string &cookie = assembled_request_cookie_;
|
std::string &cookie = assembled_request_cookie_;
|
||||||
cookie = "";
|
cookie = "";
|
||||||
|
@ -336,6 +351,9 @@ void set_last_header_value(bool &key_prev, size_t &sum, Headers &headers,
|
||||||
namespace {
|
namespace {
|
||||||
int index_headers(http2::HeaderIndex &hdidx, Headers &headers,
|
int index_headers(http2::HeaderIndex &hdidx, Headers &headers,
|
||||||
int64_t &content_length) {
|
int64_t &content_length) {
|
||||||
|
http2::init_hdidx(hdidx);
|
||||||
|
content_length = -1;
|
||||||
|
|
||||||
for (size_t i = 0; i < headers.size(); ++i) {
|
for (size_t i = 0; i < headers.size(); ++i) {
|
||||||
auto &kv = headers[i];
|
auto &kv = headers[i];
|
||||||
util::inp_strlower(kv.name);
|
util::inp_strlower(kv.name);
|
||||||
|
@ -510,6 +528,10 @@ void Downstream::set_request_http2_authority(std::string authority) {
|
||||||
request_http2_authority_ = std::move(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_major(int major) { request_major_ = major; }
|
||||||
|
|
||||||
void Downstream::set_request_minor(int minor) { request_minor_ = minor; }
|
void Downstream::set_request_minor(int minor) { request_minor_ = minor; }
|
||||||
|
@ -604,6 +626,8 @@ const Headers &Downstream::get_response_headers() const {
|
||||||
return response_headers_;
|
return response_headers_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Headers &Downstream::get_response_headers() { return response_headers_; }
|
||||||
|
|
||||||
int Downstream::index_response_headers() {
|
int Downstream::index_response_headers() {
|
||||||
return index_headers(response_hdidx_, response_headers_,
|
return index_headers(response_hdidx_, response_headers_,
|
||||||
response_content_length_);
|
response_content_length_);
|
||||||
|
@ -614,6 +638,10 @@ Downstream::get_response_header(int16_t token) const {
|
||||||
return http2::get_header(response_hdidx_, token, response_headers_);
|
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(
|
void Downstream::rewrite_location_response_header(
|
||||||
const std::string &upstream_scheme) {
|
const std::string &upstream_scheme) {
|
||||||
auto hd =
|
auto hd =
|
||||||
|
|
|
@ -96,6 +96,7 @@ public:
|
||||||
const std::string &get_http2_settings() const;
|
const std::string &get_http2_settings() const;
|
||||||
// downstream request API
|
// downstream request API
|
||||||
const Headers &get_request_headers() const;
|
const Headers &get_request_headers() const;
|
||||||
|
Headers &get_request_headers();
|
||||||
// Crumbles (split cookie by ";") in request_headers_ and returns
|
// Crumbles (split cookie by ";") in request_headers_ and returns
|
||||||
// them. Headers::no_index is inherited.
|
// them. Headers::no_index is inherited.
|
||||||
Headers crumble_request_cookie();
|
Headers crumble_request_cookie();
|
||||||
|
@ -149,15 +150,19 @@ public:
|
||||||
get_request_start_time() const;
|
get_request_start_time() const;
|
||||||
void append_request_path(const char *data, size_t len);
|
void append_request_path(const char *data, size_t len);
|
||||||
// Returns request path. For HTTP/1.1, this is request-target. For
|
// 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;
|
const std::string &get_request_path() const;
|
||||||
// Returns HTTP/2 :scheme header field value.
|
// Returns HTTP/2 :scheme header field value.
|
||||||
const std::string &get_request_http2_scheme() const;
|
const std::string &get_request_http2_scheme() const;
|
||||||
void set_request_http2_scheme(std::string scheme);
|
void set_request_http2_scheme(std::string scheme);
|
||||||
// Returns HTTP/2 :authority header field value. We also set the
|
// Returns :authority or host header field value. We may deduce it
|
||||||
// value retrieved from absolute-form HTTP/1 request.
|
// 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;
|
const std::string &get_request_http2_authority() const;
|
||||||
void set_request_http2_authority(std::string authority);
|
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_major(int major);
|
||||||
void set_request_minor(int minor);
|
void set_request_minor(int minor);
|
||||||
int get_request_major() const;
|
int get_request_major() const;
|
||||||
|
@ -207,6 +212,7 @@ public:
|
||||||
bool request_submission_ready() const;
|
bool request_submission_ready() const;
|
||||||
// downstream response API
|
// downstream response API
|
||||||
const Headers &get_response_headers() const;
|
const Headers &get_response_headers() const;
|
||||||
|
Headers &get_response_headers();
|
||||||
// Lower the response header field names and indexes response
|
// Lower the response header field names and indexes response
|
||||||
// headers. If there are invalid headers (e.g., multiple
|
// headers. If there are invalid headers (e.g., multiple
|
||||||
// Content-Length with different values), returns -1.
|
// Content-Length with different values), returns -1.
|
||||||
|
@ -216,6 +222,7 @@ public:
|
||||||
// the beginning. If no such header is found, returns nullptr.
|
// the beginning. If no such header is found, returns nullptr.
|
||||||
// This function must be called after response headers are indexed.
|
// This function must be called after response headers are indexed.
|
||||||
const Headers::value_type *get_response_header(int16_t token) const;
|
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.
|
// Rewrites the location response header field.
|
||||||
void rewrite_location_response_header(const std::string &upstream_scheme);
|
void rewrite_location_response_header(const std::string &upstream_scheme);
|
||||||
void add_response_header(std::string name, std::string value);
|
void add_response_header(std::string name, std::string value);
|
||||||
|
|
|
@ -36,6 +36,7 @@ enum ErrorCode {
|
||||||
SHRPX_ERR_NETWORK = -100,
|
SHRPX_ERR_NETWORK = -100,
|
||||||
SHRPX_ERR_EOF = -101,
|
SHRPX_ERR_EOF = -101,
|
||||||
SHRPX_ERR_INPROGRESS = -102,
|
SHRPX_ERR_INPROGRESS = -102,
|
||||||
|
SHRPX_ERR_DCONN_CANCELED = -103,
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -251,10 +251,10 @@ int Http2DownstreamConnection::push_request_headers() {
|
||||||
|
|
||||||
downstream_->set_request_pending(false);
|
downstream_->set_request_pending(false);
|
||||||
|
|
||||||
|
auto method = downstream_->get_request_method();
|
||||||
auto no_host_rewrite = get_config()->no_host_rewrite ||
|
auto no_host_rewrite = get_config()->no_host_rewrite ||
|
||||||
get_config()->http2_proxy ||
|
get_config()->http2_proxy ||
|
||||||
get_config()->client_proxy ||
|
get_config()->client_proxy || method == HTTP_CONNECT;
|
||||||
downstream_->get_request_method() == HTTP_CONNECT;
|
|
||||||
|
|
||||||
// http2session_ has already in CONNECTED state, so we can get
|
// http2session_ has already in CONNECTED state, so we can get
|
||||||
// addr_idx here.
|
// addr_idx here.
|
||||||
|
@ -265,37 +265,21 @@ int Http2DownstreamConnection::push_request_headers() {
|
||||||
.addrs[addr_idx]
|
.addrs[addr_idx]
|
||||||
.hostport.get();
|
.hostport.get();
|
||||||
|
|
||||||
const char *authority = nullptr, *host = nullptr;
|
// For HTTP/1.0 request, there is no authority in request. In that
|
||||||
if (!no_host_rewrite) {
|
// case, we use backend server's host nonetheless.
|
||||||
if (!downstream_->get_request_http2_authority().empty()) {
|
const char *authority = downstream_hostport;
|
||||||
authority = downstream_hostport;
|
auto &req_authority = downstream_->get_request_http2_authority();
|
||||||
}
|
if (no_host_rewrite && !req_authority.empty()) {
|
||||||
if (downstream_->get_request_header(http2::HD_HOST)) {
|
authority = req_authority.c_str();
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!authority && !host) {
|
if (!authority) {
|
||||||
// upstream is HTTP/1.0. We use backend server's host
|
authority = downstream_hostport;
|
||||||
// nonetheless.
|
|
||||||
host = downstream_hostport;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authority) {
|
downstream_->set_request_downstream_host(authority);
|
||||||
downstream_->set_request_downstream_host(authority);
|
|
||||||
} else {
|
|
||||||
downstream_->set_request_downstream_host(host);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t nheader = downstream_->get_request_headers().size();
|
auto nheader = downstream_->get_request_headers().size();
|
||||||
|
|
||||||
Headers cookies;
|
Headers cookies;
|
||||||
if (!get_config()->http2_no_cookie_crumbling) {
|
if (!get_config()->http2_no_cookie_crumbling) {
|
||||||
|
@ -306,7 +290,7 @@ int Http2DownstreamConnection::push_request_headers() {
|
||||||
// 1. :method
|
// 1. :method
|
||||||
// 2. :scheme
|
// 2. :scheme
|
||||||
// 3. :path
|
// 3. :path
|
||||||
// 4. :authority or host (at least either of them exists)
|
// 4. :authority
|
||||||
// 5. via (optional)
|
// 5. via (optional)
|
||||||
// 6. x-forwarded-for (optional)
|
// 6. x-forwarded-for (optional)
|
||||||
// 7. x-forwarded-proto (optional)
|
// 7. x-forwarded-proto (optional)
|
||||||
|
@ -315,33 +299,23 @@ int Http2DownstreamConnection::push_request_headers() {
|
||||||
nva.reserve(nheader + 8 + cookies.size() +
|
nva.reserve(nheader + 8 + cookies.size() +
|
||||||
get_config()->add_request_headers.size());
|
get_config()->add_request_headers.size());
|
||||||
|
|
||||||
nva.push_back(http2::make_nv_lc(
|
nva.push_back(http2::make_nv_lc(":method", http2::to_method_string(method)));
|
||||||
":method", http2::to_method_string(downstream_->get_request_method())));
|
|
||||||
|
|
||||||
auto &scheme = downstream_->get_request_http2_scheme();
|
auto &scheme = downstream_->get_request_http2_scheme();
|
||||||
|
|
||||||
if (downstream_->get_request_method() == HTTP_CONNECT) {
|
nva.push_back(http2::make_nv_lc(":authority", authority));
|
||||||
if (authority) {
|
|
||||||
nva.push_back(http2::make_nv_lc(":authority", authority));
|
if (method != HTTP_CONNECT) {
|
||||||
} else {
|
|
||||||
nva.push_back(
|
|
||||||
http2::make_nv_ls(":authority", downstream_->get_request_path()));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
assert(!scheme.empty());
|
assert(!scheme.empty());
|
||||||
|
|
||||||
nva.push_back(http2::make_nv_ls(":scheme", scheme));
|
nva.push_back(http2::make_nv_ls(":scheme", scheme));
|
||||||
|
|
||||||
if (authority) {
|
auto &path = downstream_->get_request_path();
|
||||||
nva.push_back(http2::make_nv_lc(":authority", authority));
|
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());
|
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);
|
rv = upstream->on_downstream_header_complete(downstream);
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
http2session->submit_rst_stream(frame->hd.stream_id,
|
// Handling early return (in other words, response was hijacked by
|
||||||
NGHTTP2_PROTOCOL_ERROR);
|
// mruby scripting).
|
||||||
downstream->set_response_state(Downstream::MSG_RESET);
|
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;
|
return 0;
|
||||||
|
|
|
@ -37,6 +37,9 @@
|
||||||
#include "shrpx_http.h"
|
#include "shrpx_http.h"
|
||||||
#include "shrpx_worker.h"
|
#include "shrpx_worker.h"
|
||||||
#include "shrpx_http2_session.h"
|
#include "shrpx_http2_session.h"
|
||||||
|
#ifdef HAVE_MRUBY
|
||||||
|
#include "shrpx_mruby.h"
|
||||||
|
#endif // HAVE_MRUBY
|
||||||
#include "http2.h"
|
#include "http2.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "base64.h"
|
#include "base64.h"
|
||||||
|
@ -291,9 +294,16 @@ int Http2Upstream::on_request_headers(Downstream *downstream,
|
||||||
|
|
||||||
downstream->set_request_method(method_token);
|
downstream->set_request_method(method_token);
|
||||||
downstream->set_request_http2_scheme(http2::value_to_str(scheme));
|
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));
|
downstream->set_request_http2_authority(http2::value_to_str(authority));
|
||||||
|
|
||||||
if (path) {
|
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));
|
downstream->set_request_path(http2::value_to_str(path));
|
||||||
} else {
|
} else {
|
||||||
auto &value = path->value;
|
auto &value = path->value;
|
||||||
|
@ -309,12 +319,31 @@ int Http2Upstream::on_request_headers(Downstream *downstream,
|
||||||
downstream->inspect_http2_request();
|
downstream->inspect_http2_request();
|
||||||
|
|
||||||
downstream->set_request_state(Downstream::HEADER_COMPLETE);
|
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) {
|
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
||||||
downstream->disable_upstream_rtimer();
|
downstream->disable_upstream_rtimer();
|
||||||
|
|
||||||
downstream->set_request_state(Downstream::MSG_COMPLETE);
|
downstream->set_request_state(Downstream::MSG_COMPLETE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
start_downstream(downstream);
|
start_downstream(downstream);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -558,6 +587,20 @@ int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
||||||
// downstream is in pending queue.
|
// downstream is in pending queue.
|
||||||
auto ptr = downstream.get();
|
auto ptr = downstream.get();
|
||||||
upstream->add_pending_downstream(std::move(downstream));
|
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);
|
upstream->start_downstream(ptr);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -898,6 +941,11 @@ int Http2Upstream::downstream_read(DownstreamConnection *dconn) {
|
||||||
if (rv == SHRPX_ERR_EOF) {
|
if (rv == SHRPX_ERR_EOF) {
|
||||||
return downstream_eof(dconn);
|
return downstream_eof(dconn);
|
||||||
}
|
}
|
||||||
|
if (rv == SHRPX_ERR_DCONN_CANCELED) {
|
||||||
|
downstream->pop_downstream_connection();
|
||||||
|
handler_->signal_write();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
if (rv != SHRPX_ERR_NETWORK) {
|
if (rv != SHRPX_ERR_NETWORK) {
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
@ -1120,6 +1168,63 @@ ssize_t downstream_data_read_callback(nghttp2_session *session,
|
||||||
}
|
}
|
||||||
} // namespace
|
} // 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,
|
int Http2Upstream::error_reply(Downstream *downstream,
|
||||||
unsigned int status_code) {
|
unsigned int status_code) {
|
||||||
int rv;
|
int rv;
|
||||||
|
@ -1191,6 +1296,24 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
|
||||||
downstream->get_request_http2_scheme());
|
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;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // HAVE_MRUBY
|
||||||
|
|
||||||
size_t nheader = downstream->get_response_headers().size();
|
size_t nheader = downstream->get_response_headers().size();
|
||||||
auto nva = std::vector<nghttp2_nv>();
|
auto nva = std::vector<nghttp2_nv>();
|
||||||
// 3 means :status and possible server and via header field.
|
// 3 means :status and possible server and via header field.
|
||||||
|
@ -1272,12 +1395,9 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
|
||||||
// We need some conditions that must be fulfilled to initiate server
|
// We need some conditions that must be fulfilled to initiate server
|
||||||
// push.
|
// push.
|
||||||
//
|
//
|
||||||
// * Server push is disabled for http2 proxy, since incoming headers
|
// * Server push is disabled for http2 proxy or client proxy, since
|
||||||
// are mixed origins. We don't know how to reliably determine the
|
// incoming headers are mixed origins. We don't know how to
|
||||||
// authority yet.
|
// 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.
|
|
||||||
//
|
//
|
||||||
// * We need 200 response code for associated resource. This is too
|
// * We need 200 response code for associated resource. This is too
|
||||||
// restrictive, we will review this later.
|
// restrictive, we will review this later.
|
||||||
|
@ -1288,8 +1408,8 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
|
||||||
if (!get_config()->no_server_push &&
|
if (!get_config()->no_server_push &&
|
||||||
nghttp2_session_get_remote_settings(session_,
|
nghttp2_session_get_remote_settings(session_,
|
||||||
NGHTTP2_SETTINGS_ENABLE_PUSH) == 1 &&
|
NGHTTP2_SETTINGS_ENABLE_PUSH) == 1 &&
|
||||||
get_config()->downstream_proto == PROTO_HTTP &&
|
!get_config()->http2_proxy && !get_config()->client_proxy &&
|
||||||
!get_config()->http2_proxy && (downstream->get_stream_id() % 2) &&
|
(downstream->get_stream_id() % 2) &&
|
||||||
downstream->get_response_header(http2::HD_LINK) &&
|
downstream->get_response_header(http2::HD_LINK) &&
|
||||||
downstream->get_response_http_status() == 200 &&
|
downstream->get_response_http_status() == 200 &&
|
||||||
(downstream->get_request_method() == HTTP_GET ||
|
(downstream->get_request_method() == HTTP_GET ||
|
||||||
|
@ -1473,80 +1593,31 @@ int Http2Upstream::on_downstream_reset(bool no_retry) {
|
||||||
|
|
||||||
int Http2Upstream::prepare_push_promise(Downstream *downstream) {
|
int Http2Upstream::prepare_push_promise(Downstream *downstream) {
|
||||||
int rv;
|
int rv;
|
||||||
http_parser_url u{};
|
const char *base;
|
||||||
rv = http_parser_parse_url(downstream->get_request_path().c_str(),
|
size_t baselen;
|
||||||
downstream->get_request_path().size(), 0, &u);
|
|
||||||
|
rv = http2::get_pure_path_component(&base, &baselen,
|
||||||
|
downstream->get_request_path());
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
return 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()) {
|
for (auto &kv : downstream->get_response_headers()) {
|
||||||
if (kv.token != http2::HD_LINK) {
|
if (kv.token != http2::HD_LINK) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (auto &link :
|
for (auto &link :
|
||||||
http2::parse_link_header(kv.value.c_str(), kv.value.size())) {
|
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;
|
auto uri = link.uri.first;
|
||||||
size_t rellen;
|
auto len = link.uri.second - link.uri.first;
|
||||||
const char *relq = nullptr;
|
|
||||||
size_t relqlen = 0;
|
|
||||||
|
|
||||||
std::string authority, scheme;
|
std::string scheme, authority, path;
|
||||||
http_parser_url v{};
|
|
||||||
rv = http_parser_parse_url(link_url, link_urllen, 0, &v);
|
rv = http2::construct_push_component(scheme, authority, path, base,
|
||||||
|
baselen, uri, len);
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
assert(link_urllen);
|
continue;
|
||||||
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_SCHEMA)) {
|
|
||||||
http2::copy_url_component(scheme, &v, UF_SCHEMA, link_url);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (v.field_set & (1 << UF_HOST)) {
|
|
||||||
http2::copy_url_component(authority, &v, UF_HOST, link_url);
|
|
||||||
if (v.field_set & (1 << UF_PORT)) {
|
|
||||||
authority += ":";
|
|
||||||
authority += util::utos(v.port);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scheme.empty()) {
|
if (scheme.empty()) {
|
||||||
|
@ -1557,8 +1628,6 @@ int Http2Upstream::prepare_push_promise(Downstream *downstream) {
|
||||||
authority = downstream->get_request_http2_authority();
|
authority = downstream->get_request_http2_authority();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto path = http2::path_join(base, baselen, nullptr, 0, rel, rellen, relq,
|
|
||||||
relqlen);
|
|
||||||
rv = submit_push_promise(scheme, authority, path, downstream);
|
rv = submit_push_promise(scheme, authority, path, downstream);
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -1627,4 +1696,50 @@ int Http2Upstream::submit_push_promise(const std::string &scheme,
|
||||||
return 0;
|
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
|
} // namespace shrpx
|
||||||
|
|
|
@ -78,6 +78,10 @@ public:
|
||||||
|
|
||||||
virtual void on_handler_delete();
|
virtual void on_handler_delete();
|
||||||
virtual int on_downstream_reset(bool no_retry);
|
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;
|
bool get_flow_control() const;
|
||||||
// Perform HTTP/2 upgrade from |upstream|. On success, this object
|
// Perform HTTP/2 upgrade from |upstream|. On success, this object
|
||||||
|
|
|
@ -209,89 +209,54 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int HttpDownstreamConnection::push_request_headers() {
|
int HttpDownstreamConnection::push_request_headers() {
|
||||||
const char *authority = nullptr, *host = nullptr;
|
|
||||||
auto downstream_hostport = get_config()
|
auto downstream_hostport = get_config()
|
||||||
->downstream_addr_groups[group_]
|
->downstream_addr_groups[group_]
|
||||||
.addrs[addr_idx_]
|
.addrs[addr_idx_]
|
||||||
.hostport.get();
|
.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 &&
|
// For HTTP/1.0 request, there is no authority in request. In that
|
||||||
!get_config()->client_proxy && !connect_method) {
|
// case, we use backend server's host nonetheless.
|
||||||
if (!downstream_->get_request_http2_authority().empty()) {
|
const char *authority = downstream_hostport;
|
||||||
authority = downstream_hostport;
|
auto &req_authority = downstream_->get_request_http2_authority();
|
||||||
}
|
auto no_host_rewrite = get_config()->no_host_rewrite ||
|
||||||
if (downstream_->get_request_header(http2::HD_HOST)) {
|
get_config()->http2_proxy ||
|
||||||
host = downstream_hostport;
|
get_config()->client_proxy || connect_method;
|
||||||
}
|
|
||||||
} else {
|
if (no_host_rewrite && !req_authority.empty()) {
|
||||||
if (!downstream_->get_request_http2_authority().empty()) {
|
authority = req_authority.c_str();
|
||||||
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) {
|
downstream_->set_request_downstream_host(authority);
|
||||||
// upstream is HTTP/1.0. We use backend server's host
|
|
||||||
// nonetheless.
|
|
||||||
host = downstream_hostport;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authority) {
|
|
||||||
downstream_->set_request_downstream_host(authority);
|
|
||||||
} else {
|
|
||||||
downstream_->set_request_downstream_host(host);
|
|
||||||
}
|
|
||||||
|
|
||||||
downstream_->assemble_request_cookie();
|
downstream_->assemble_request_cookie();
|
||||||
|
|
||||||
// Assume that method and request path do not contain \r\n.
|
// Assume that method and request path do not contain \r\n.
|
||||||
std::string hdrs = http2::to_method_string(downstream_->get_request_method());
|
std::string hdrs = http2::to_method_string(method);
|
||||||
hdrs += ' ';
|
hdrs += ' ';
|
||||||
|
|
||||||
auto &scheme = downstream_->get_request_http2_scheme();
|
auto &scheme = downstream_->get_request_http2_scheme();
|
||||||
|
auto &path = downstream_->get_request_path();
|
||||||
|
|
||||||
if (connect_method) {
|
if (connect_method) {
|
||||||
if (authority) {
|
hdrs += authority;
|
||||||
hdrs += authority;
|
|
||||||
} else {
|
|
||||||
hdrs += downstream_->get_request_path();
|
|
||||||
}
|
|
||||||
} else if (get_config()->http2_proxy || get_config()->client_proxy) {
|
} else if (get_config()->http2_proxy || get_config()->client_proxy) {
|
||||||
// Construct absolute-form request target because we are going to
|
// Construct absolute-form request target because we are going to
|
||||||
// send a request to a HTTP/1 proxy.
|
// send a request to a HTTP/1 proxy.
|
||||||
assert(!scheme.empty());
|
assert(!scheme.empty());
|
||||||
hdrs += scheme;
|
hdrs += scheme;
|
||||||
hdrs += "://";
|
hdrs += "://";
|
||||||
|
hdrs += authority;
|
||||||
if (authority) {
|
hdrs += path;
|
||||||
hdrs += authority;
|
} else if (method == HTTP_OPTIONS && path.empty()) {
|
||||||
} else {
|
// Server-wide OPTIONS
|
||||||
hdrs += host;
|
hdrs += "*";
|
||||||
}
|
|
||||||
|
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// No proxy case.
|
hdrs += path;
|
||||||
hdrs += downstream_->get_request_path();
|
|
||||||
}
|
}
|
||||||
hdrs += " HTTP/1.1\r\nHost: ";
|
hdrs += " HTTP/1.1\r\nHost: ";
|
||||||
if (authority) {
|
hdrs += authority;
|
||||||
hdrs += authority;
|
|
||||||
} else {
|
|
||||||
hdrs += host;
|
|
||||||
}
|
|
||||||
hdrs += "\r\n";
|
hdrs += "\r\n";
|
||||||
|
|
||||||
http2::build_http1_headers_from_headers(hdrs,
|
http2::build_http1_headers_from_headers(hdrs,
|
||||||
|
@ -774,6 +739,12 @@ int HttpDownstreamConnection::on_read() {
|
||||||
auto htperr = HTTP_PARSER_ERRNO(&response_htp_);
|
auto htperr = HTTP_PARSER_ERRNO(&response_htp_);
|
||||||
|
|
||||||
if (htperr != HPE_OK) {
|
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)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
DCLOG(INFO, this) << "HTTP parser failure: "
|
DCLOG(INFO, this) << "HTTP parser failure: "
|
||||||
<< "(" << http_errno_name(htperr) << ") "
|
<< "(" << http_errno_name(htperr) << ") "
|
||||||
|
|
|
@ -37,6 +37,9 @@
|
||||||
#include "shrpx_log_config.h"
|
#include "shrpx_log_config.h"
|
||||||
#include "shrpx_worker.h"
|
#include "shrpx_worker.h"
|
||||||
#include "shrpx_http2_session.h"
|
#include "shrpx_http2_session.h"
|
||||||
|
#ifdef HAVE_MRUBY
|
||||||
|
#include "shrpx_mruby.h"
|
||||||
|
#endif // HAVE_MRUBY
|
||||||
#include "http2.h"
|
#include "http2.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "template.h"
|
#include "template.h"
|
||||||
|
@ -69,8 +72,14 @@ int htp_msg_begin(http_parser *htp) {
|
||||||
auto handler = upstream->get_client_handler();
|
auto handler = upstream->get_client_handler();
|
||||||
|
|
||||||
// TODO specify 0 as priority for now
|
// TODO specify 0 as priority for now
|
||||||
upstream->attach_downstream(
|
auto downstream =
|
||||||
make_unique<Downstream>(upstream, handler->get_mcpool(), 0, 0));
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -90,7 +99,12 @@ int htp_uricb(http_parser *htp, const char *data, size_t len) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
downstream->add_request_headers_sum(len);
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -212,7 +226,7 @@ void rewrite_request_host_path_from_uri(Downstream *downstream, const char *uri,
|
||||||
//
|
//
|
||||||
// Notice that no slash after authority. See
|
// Notice that no slash after authority. See
|
||||||
// http://tools.ietf.org/html/rfc7230#section-5.3.4
|
// http://tools.ietf.org/html/rfc7230#section-5.3.4
|
||||||
downstream->set_request_path("*");
|
downstream->set_request_path("");
|
||||||
// we ignore query component here
|
// we ignore query component here
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
|
@ -241,17 +255,18 @@ int htp_hdrs_completecb(http_parser *htp) {
|
||||||
}
|
}
|
||||||
auto downstream = upstream->get_downstream();
|
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_major(htp->http_major);
|
||||||
downstream->set_request_minor(htp->http_minor);
|
downstream->set_request_minor(htp->http_minor);
|
||||||
|
|
||||||
downstream->set_request_connection_close(!http_should_keep_alive(htp));
|
downstream->set_request_connection_close(!http_should_keep_alive(htp));
|
||||||
|
|
||||||
|
auto method = downstream->get_request_method();
|
||||||
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << http2::to_method_string(downstream->get_request_method()) << " "
|
ss << http2::to_method_string(method) << " "
|
||||||
<< downstream->get_request_path() << " "
|
<< (method == HTTP_CONNECT ? downstream->get_request_http2_authority()
|
||||||
|
: downstream->get_request_path()) << " "
|
||||||
<< "HTTP/" << downstream->get_request_major() << "."
|
<< "HTTP/" << downstream->get_request_major() << "."
|
||||||
<< downstream->get_request_minor() << "\n";
|
<< downstream->get_request_minor() << "\n";
|
||||||
const auto &headers = downstream->get_request_headers();
|
const auto &headers = downstream->get_request_headers();
|
||||||
|
@ -274,13 +289,12 @@ int htp_hdrs_completecb(http_parser *htp) {
|
||||||
|
|
||||||
downstream->inspect_http1_request();
|
downstream->inspect_http1_request();
|
||||||
|
|
||||||
if (downstream->get_request_method() != HTTP_CONNECT) {
|
if (method != HTTP_CONNECT) {
|
||||||
http_parser_url u{};
|
http_parser_url u{};
|
||||||
// make a copy of request path, since we may set request path
|
// make a copy of request path, since we may set request path
|
||||||
// while we are refering to original request path.
|
// while we are refering to original request path.
|
||||||
auto uri = downstream->get_request_path();
|
auto path = downstream->get_request_path();
|
||||||
rv = http_parser_parse_url(uri.c_str(),
|
rv = http_parser_parse_url(path.c_str(), path.size(), 0, &u);
|
||||||
downstream->get_request_path().size(), 0, &u);
|
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
// Expect to respond with 400 bad request
|
// Expect to respond with 400 bad request
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -292,8 +306,17 @@ int htp_hdrs_completecb(http_parser *htp) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
downstream->set_request_path(
|
if (method == HTTP_OPTIONS && path == "*") {
|
||||||
http2::rewrite_clean_path(std::begin(uri), std::end(uri)));
|
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()) {
|
if (upstream->get_client_handler()->get_ssl()) {
|
||||||
downstream->set_request_http2_scheme("https");
|
downstream->set_request_http2_scheme("https");
|
||||||
|
@ -301,10 +324,29 @@ int htp_hdrs_completecb(http_parser *htp) {
|
||||||
downstream->set_request_http2_scheme("http");
|
downstream->set_request_http2_scheme("http");
|
||||||
}
|
}
|
||||||
} else {
|
} 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(
|
rv = downstream->attach_downstream_connection(
|
||||||
upstream->get_client_handler()->get_downstream_connection(downstream));
|
upstream->get_client_handler()->get_downstream_connection(downstream));
|
||||||
|
|
||||||
|
@ -320,8 +362,6 @@ int htp_hdrs_completecb(http_parser *htp) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
downstream->set_request_state(Downstream::HEADER_COMPLETE);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -352,6 +392,17 @@ int htp_msg_completecb(http_parser *htp) {
|
||||||
downstream->set_request_state(Downstream::MSG_COMPLETE);
|
downstream->set_request_state(Downstream::MSG_COMPLETE);
|
||||||
rv = downstream->end_upload_data();
|
rv = downstream->end_upload_data();
|
||||||
if (rv != 0) {
|
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;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -451,6 +502,13 @@ int HttpsUpstream::on_read() {
|
||||||
auto htperr = HTTP_PARSER_ERRNO(&htp_);
|
auto htperr = HTTP_PARSER_ERRNO(&htp_);
|
||||||
|
|
||||||
if (htperr == HPE_PAUSED) {
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -472,13 +530,16 @@ int HttpsUpstream::on_read() {
|
||||||
if (htperr == HPE_INVALID_METHOD) {
|
if (htperr == HPE_INVALID_METHOD) {
|
||||||
status_code = 501;
|
status_code = 501;
|
||||||
} else if (downstream) {
|
} else if (downstream) {
|
||||||
if (downstream->get_request_state() == Downstream::CONNECT_FAIL) {
|
status_code = downstream->get_response_http_status();
|
||||||
status_code = 503;
|
if (status_code == 0) {
|
||||||
} else if (downstream->get_request_state() ==
|
if (downstream->get_request_state() == Downstream::CONNECT_FAIL) {
|
||||||
Downstream::HTTP1_REQUEST_HEADER_TOO_LARGE) {
|
status_code = 503;
|
||||||
status_code = 431;
|
} else if (downstream->get_request_state() ==
|
||||||
} else {
|
Downstream::HTTP1_REQUEST_HEADER_TOO_LARGE) {
|
||||||
status_code = 400;
|
status_code = 431;
|
||||||
|
} else {
|
||||||
|
status_code = 400;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
status_code = 400;
|
status_code = 400;
|
||||||
|
@ -552,6 +613,11 @@ int HttpsUpstream::on_write() {
|
||||||
// We need this if response ends before request.
|
// We need this if response ends before request.
|
||||||
if (downstream->get_request_state() == Downstream::MSG_COMPLETE) {
|
if (downstream->get_request_state() == Downstream::MSG_COMPLETE) {
|
||||||
delete_downstream();
|
delete_downstream();
|
||||||
|
|
||||||
|
if (handler_->get_should_close_after_write()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
return resume_read(SHRPX_NO_BUFFER, nullptr, 0);
|
return resume_read(SHRPX_NO_BUFFER, nullptr, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -594,6 +660,11 @@ int HttpsUpstream::downstream_read(DownstreamConnection *dconn) {
|
||||||
return downstream_eof(dconn);
|
return downstream_eof(dconn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (rv == SHRPX_ERR_DCONN_CANCELED) {
|
||||||
|
downstream->pop_downstream_connection();
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
if (rv < 0) {
|
if (rv < 0) {
|
||||||
return downstream_error(dconn, Downstream::EVENT_ERROR);
|
return downstream_error(dconn, Downstream::EVENT_ERROR);
|
||||||
}
|
}
|
||||||
|
@ -703,6 +774,63 @@ int HttpsUpstream::downstream_error(DownstreamConnection *dconn, int events) {
|
||||||
return 0;
|
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 ");
|
||||||
|
auto status_str =
|
||||||
|
http2::get_status_string(downstream->get_response_http_status());
|
||||||
|
output->append(status_str.c_str(), status_str.size());
|
||||||
|
output->append("\r\n");
|
||||||
|
|
||||||
|
for (auto &kv : downstream->get_response_headers()) {
|
||||||
|
if (kv.name.empty() || kv.name[0] == ':') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto name = kv.name;
|
||||||
|
http2::capitalize(name, 0);
|
||||||
|
output->append(name.c_str(), name.size());
|
||||||
|
output->append(": ");
|
||||||
|
output->append(kv.value.c_str(), kv.value.size());
|
||||||
|
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) {
|
void HttpsUpstream::error_reply(unsigned int status_code) {
|
||||||
auto html = http::create_error_html(status_code);
|
auto html = http::create_error_html(status_code);
|
||||||
auto downstream = get_downstream();
|
auto downstream = get_downstream();
|
||||||
|
@ -765,6 +893,22 @@ 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;
|
auto connect_method = downstream->get_request_method() == HTTP_CONNECT;
|
||||||
|
|
||||||
std::string hdrs = "HTTP/";
|
std::string hdrs = "HTTP/";
|
||||||
|
@ -1016,4 +1160,9 @@ fail:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int HttpsUpstream::initiate_push(Downstream *downstream, const char *uri,
|
||||||
|
size_t len) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -74,6 +74,10 @@ public:
|
||||||
|
|
||||||
virtual void on_handler_delete();
|
virtual void on_handler_delete();
|
||||||
virtual int on_downstream_reset(bool no_retry);
|
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 reset_current_header_length();
|
||||||
void log_response_headers(const std::string &hdrs) const;
|
void log_response_headers(const std::string &hdrs) const;
|
||||||
|
|
|
@ -0,0 +1,203 @@
|
||||||
|
/*
|
||||||
|
* 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() { 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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,255 @@
|
||||||
|
/*
|
||||||
|
* 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
mrb_value val;
|
||||||
|
mrb_get_args(mrb, "|o", &val);
|
||||||
|
|
||||||
|
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() && !mrb_nil_p(val)) {
|
||||||
|
body = reinterpret_cast<const uint8_t *>(RSTRING_PTR(val));
|
||||||
|
bodylen = RSTRING_LEN(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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
|
|
@ -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_downstream_connection.h"
|
||||||
#include "shrpx_config.h"
|
#include "shrpx_config.h"
|
||||||
#include "shrpx_http.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 "http2.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "template.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);
|
downstream->set_request_http2_authority(host->value);
|
||||||
if (get_config()->http2_proxy || get_config()->client_proxy) {
|
if (get_config()->http2_proxy || get_config()->client_proxy) {
|
||||||
downstream->set_request_path(path->value);
|
downstream->set_request_path(path->value);
|
||||||
|
} else if (method_token == HTTP_OPTIONS && path->value == "*") {
|
||||||
|
// Server-wide OPTIONS request. Path is empty.
|
||||||
} else {
|
} else {
|
||||||
downstream->set_request_path(http2::rewrite_clean_path(
|
downstream->set_request_path(http2::rewrite_clean_path(
|
||||||
std::begin(path->value), std::end(path->value)));
|
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->inspect_http2_request();
|
||||||
|
|
||||||
downstream->set_request_state(Downstream::HEADER_COMPLETE);
|
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 (frame->syn_stream.hd.flags & SPDYLAY_CTRL_FLAG_FIN) {
|
||||||
if (!downstream->validate_request_bodylen()) {
|
if (!downstream->validate_request_bodylen()) {
|
||||||
upstream->rst_stream(downstream, SPDYLAY_PROTOCOL_ERROR);
|
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);
|
downstream->set_request_state(Downstream::MSG_COMPLETE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
upstream->start_downstream(downstream);
|
upstream->start_downstream(downstream);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -574,6 +600,11 @@ int SpdyUpstream::downstream_read(DownstreamConnection *dconn) {
|
||||||
if (rv == SHRPX_ERR_EOF) {
|
if (rv == SHRPX_ERR_EOF) {
|
||||||
return downstream_eof(dconn);
|
return downstream_eof(dconn);
|
||||||
}
|
}
|
||||||
|
if (rv == SHRPX_ERR_DCONN_CANCELED) {
|
||||||
|
downstream->pop_downstream_connection();
|
||||||
|
handler_->signal_write();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
if (rv != SHRPX_ERR_NETWORK) {
|
if (rv != SHRPX_ERR_NETWORK) {
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
@ -773,6 +804,70 @@ ssize_t spdy_data_read_callback(spdylay_session *session, int32_t stream_id,
|
||||||
}
|
}
|
||||||
} // namespace
|
} // 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,
|
int SpdyUpstream::error_reply(Downstream *downstream,
|
||||||
unsigned int status_code) {
|
unsigned int status_code) {
|
||||||
int rv;
|
int rv;
|
||||||
|
@ -845,6 +940,22 @@ int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) {
|
||||||
return 0;
|
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;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
#endif // HAVE_MRUBY
|
||||||
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
DLOG(INFO, downstream) << "HTTP response header completed";
|
DLOG(INFO, downstream) << "HTTP response header completed";
|
||||||
}
|
}
|
||||||
|
@ -1092,4 +1203,9 @@ int SpdyUpstream::on_downstream_reset(bool no_retry) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int SpdyUpstream::initiate_push(Downstream *downstream, const char *uri,
|
||||||
|
size_t len) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -74,6 +74,11 @@ public:
|
||||||
virtual void on_handler_delete();
|
virtual void on_handler_delete();
|
||||||
virtual int on_downstream_reset(bool no_retry);
|
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;
|
bool get_flow_control() const;
|
||||||
|
|
||||||
int consume(int32_t stream_id, size_t len);
|
int consume(int32_t stream_id, size_t len);
|
||||||
|
|
|
@ -62,6 +62,11 @@ public:
|
||||||
virtual void pause_read(IOCtrlReason reason) = 0;
|
virtual void pause_read(IOCtrlReason reason) = 0;
|
||||||
virtual int resume_read(IOCtrlReason reason, Downstream *downstream,
|
virtual int resume_read(IOCtrlReason reason, Downstream *downstream,
|
||||||
size_t consumed) = 0;
|
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
|
} // namespace shrpx
|
||||||
|
|
|
@ -37,6 +37,9 @@
|
||||||
#include "shrpx_log_config.h"
|
#include "shrpx_log_config.h"
|
||||||
#include "shrpx_connect_blocker.h"
|
#include "shrpx_connect_blocker.h"
|
||||||
#include "shrpx_memcached_dispatcher.h"
|
#include "shrpx_memcached_dispatcher.h"
|
||||||
|
#ifdef HAVE_MRUBY
|
||||||
|
#include "shrpx_mruby.h"
|
||||||
|
#endif // HAVE_MRUBY
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "template.h"
|
#include "template.h"
|
||||||
|
|
||||||
|
@ -264,4 +267,19 @@ MemcachedDispatcher *Worker::get_session_cache_memcached_dispatcher() {
|
||||||
return session_cache_memcached_dispatcher_.get();
|
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
|
} // namespace shrpx
|
||||||
|
|
|
@ -51,6 +51,14 @@ class Http2Session;
|
||||||
class ConnectBlocker;
|
class ConnectBlocker;
|
||||||
class MemcachedDispatcher;
|
class MemcachedDispatcher;
|
||||||
|
|
||||||
|
#ifdef HAVE_MRUBY
|
||||||
|
namespace mruby {
|
||||||
|
|
||||||
|
class MRubyContext;
|
||||||
|
|
||||||
|
} // namespace mruby
|
||||||
|
#endif // HAVE_MRUBY
|
||||||
|
|
||||||
namespace ssl {
|
namespace ssl {
|
||||||
class CertLookupTree;
|
class CertLookupTree;
|
||||||
} // namespace ssl
|
} // namespace ssl
|
||||||
|
@ -124,6 +132,12 @@ public:
|
||||||
|
|
||||||
MemcachedDispatcher *get_session_cache_memcached_dispatcher();
|
MemcachedDispatcher *get_session_cache_memcached_dispatcher();
|
||||||
|
|
||||||
|
#ifdef HAVE_MRUBY
|
||||||
|
int create_mruby_context();
|
||||||
|
|
||||||
|
mruby::MRubyContext *get_mruby_context() const;
|
||||||
|
#endif // HAVE_MRUBY
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#ifndef NOTHREADS
|
#ifndef NOTHREADS
|
||||||
std::future<void> fut_;
|
std::future<void> fut_;
|
||||||
|
@ -137,6 +151,9 @@ private:
|
||||||
WorkerStat worker_stat_;
|
WorkerStat worker_stat_;
|
||||||
std::vector<DownstreamGroup> dgrps_;
|
std::vector<DownstreamGroup> dgrps_;
|
||||||
std::unique_ptr<MemcachedDispatcher> session_cache_memcached_dispatcher_;
|
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_;
|
struct ev_loop *loop_;
|
||||||
|
|
||||||
// Following fields are shared across threads if
|
// Following fields are shared across threads if
|
||||||
|
|
|
@ -30,5 +30,23 @@ libhttp_parser_la_SOURCES = \
|
||||||
http-parser/http_parser.c \
|
http-parser/http_parser.c \
|
||||||
http-parser/http_parser.h
|
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" \
|
||||||
|
CC="${CC}" CXX="${CXX}" LD="${LD}" \
|
||||||
|
"${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
|
endif # ENABLE_THIRD_PARTY
|
||||||
|
|
|
@ -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
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 1cbbb7e11c02d381a6b76aeebae8db0f54ae9baf
|
Loading…
Reference in New Issue