Merge branch 'master' into v1.0.0

Conflicts:
	lib/nghttp2_option.h
	lib/nghttp2_session.h
	src/HttpServer.cc
This commit is contained in:
Tatsuhiro Tsujikawa 2015-05-08 19:21:51 +09:00
commit e63d6e490a
56 changed files with 964 additions and 260 deletions

View File

@ -179,7 +179,7 @@ https://nghttp2.org/documentation/
Unit tests Unit tests
---------- ----------
Unit tests are done by simply running `make check`. Unit tests are done by simply running ``make check``.
Integration tests Integration tests
----------------- -----------------
@ -1364,7 +1364,7 @@ original creator(s) or those who have been assigned copyright by the
original author(s). original author(s).
By submitting a patch to the nghttp2 project, you (or your employer, as By submitting a patch to the nghttp2 project, you (or your employer, as
the case may be) agree to assign the copyright of your submission to us. the case may be) agree to assign the copyright of your submission to us.
.. the above really needs to be reworded to pass legal muster. .. the above really needs to be reworded to pass legal muster.
We will credit you for your We will credit you for your
changes as far as possible, to give credit but also to keep a trace changes as far as possible, to give credit but also to keep a trace

View File

@ -25,13 +25,13 @@ dnl Do not change user variables!
dnl http://www.gnu.org/software/automake/manual/html_node/Flag-Variables-Ordering.html dnl http://www.gnu.org/software/automake/manual/html_node/Flag-Variables-Ordering.html
AC_PREREQ(2.61) AC_PREREQ(2.61)
AC_INIT([nghttp2], [0.7.14-DEV], [t-tujikawa@users.sourceforge.net]) AC_INIT([nghttp2], [0.7.15-DEV], [t-tujikawa@users.sourceforge.net])
LT_PREREQ([2.2.6]) LT_PREREQ([2.2.6])
LT_INIT() LT_INIT()
dnl See versioning rule: dnl See versioning rule:
dnl http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html dnl http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
AC_SUBST(LT_CURRENT, 13) AC_SUBST(LT_CURRENT, 13)
AC_SUBST(LT_REVISION, 2) AC_SUBST(LT_REVISION, 3)
AC_SUBST(LT_AGE, 8) AC_SUBST(LT_AGE, 8)
major=`echo $PACKAGE_VERSION |cut -d. -f1 | sed -e "s/[^0-9]//g"` major=`echo $PACKAGE_VERSION |cut -d. -f1 | sed -e "s/[^0-9]//g"`

2
contrib/.gitignore vendored
View File

@ -1 +1,3 @@
nghttpx-init nghttpx-init
nghttpx.service
nghttpx-upstart.conf

View File

@ -21,19 +21,24 @@
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
EXTRA_DIST = nghttpx-init.in nghttpx-logrotate configfiles = nghttpx-init nghttpx.service nghttpx-upstart.conf
EXTRA_DIST = $(configfiles:%=%.in) nghttpx-logrotate
edit = sed -e 's|@bindir[@]|$(bindir)|g' edit = sed -e 's|@bindir[@]|$(bindir)|g'
nghttpx-init: Makefile nghttpx-init: %: $(srcdir)/%.in
rm -f $@ $@.tmp rm -f $@ $@.tmp
$(edit) $(srcdir)/$@.in > $@.tmp $(edit) $< > $@.tmp
chmod +x $@.tmp chmod +x $@.tmp
mv $@.tmp $@ mv $@.tmp $@
nghttpx-init: $(srcdir)/nghttpx-init.in nghttpx.service nghttpx-upstart.conf: %: $(srcdir)/%.in
$(edit) $< > $@
all-local: nghttpx-init $(configfiles): Makefile
all-local: $(configfiles)
clean-local: clean-local:
-rm -f nghttpx-init nghttpx-init.tmp -rm -f nghttpx-init.tmp $(configfiles)

View File

@ -1,18 +1,11 @@
/var/log/nghttpx/*.log { /var/log/nghttpx/*.log {
weekly weekly
missingok rotate 52
rotate 52 missingok
compress compress
delaycompress delaycompress
notifempty notifempty
create 0640 www-data adm postrotate
sharedscripts killall -USR1 nghttpx 2> /dev/null || true
prerotate endscript
if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
run-parts /etc/logrotate.d/httpd-prerotate; \
fi \
endscript
postrotate
[ -s /run/nghttpx.pid ] && kill -USR1 `cat /run/nghttpx.pid`
endscript
} }

View File

@ -0,0 +1,8 @@
# vim: ft=upstart:
description "HTTP/2 reverse proxy"
start on runlevel [2]
stop on runlevel [016]
exec @bindir@/nghttpx

View File

@ -0,0 +1,10 @@
[Unit]
Description=HTTP/2 experimental proxy
After=network.target
[Service]
Type=simple
ExecStart=@bindir@/nghttpx --errorlog-syslog
[Install]
WantedBy=multi-user.target

View File

@ -5,7 +5,7 @@ From https://github.com/ryan-roemer/sphinx-bootstrap-theme.
""" """
import os import os
VERSION = (0, 1, 7) VERSION = (0, 1, 8)
__version__ = ".".join(str(v) for v in VERSION) __version__ = ".".join(str(v) for v in VERSION)
__version_full__ = __version__ __version_full__ = __version__

View File

@ -6,14 +6,16 @@
{% endfor %} {% endfor %}
<li>{{ title }}</li> <li>{{ title }}</li>
<li class="wy-breadcrumbs-aside"> <li class="wy-breadcrumbs-aside">
{% if display_github %} {% if pagename != "search" %}
<a href="https://{{ github_host|default("github.com") }}/{{ github_user }}/{{ github_repo }}/blob/{{ github_version }}{{ conf_py_path }}{{ pagename }}{{ source_suffix }}" class="fa fa-github"> Edit on GitHub</a> {% if display_github %}
{% elif display_bitbucket %} <a href="https://{{ github_host|default("github.com") }}/{{ github_user }}/{{ github_repo }}/blob/{{ github_version }}{{ conf_py_path }}{{ pagename }}{{ source_suffix }}" class="fa fa-github"> Edit on GitHub</a>
<a href="https://bitbucket.org/{{ bitbucket_user }}/{{ bitbucket_repo }}/src/{{ bitbucket_version}}{{ conf_py_path }}{{ pagename }}{{ source_suffix }}" class="fa fa-bitbucket"> Edit on Bitbucket</a> {% elif display_bitbucket %}
{% elif show_source and source_url_prefix %} <a href="https://bitbucket.org/{{ bitbucket_user }}/{{ bitbucket_repo }}/src/{{ bitbucket_version}}{{ conf_py_path }}{{ pagename }}{{ source_suffix }}" class="fa fa-bitbucket"> Edit on Bitbucket</a>
<a href="{{ source_url_prefix }}{{ pagename }}{{ source_suffix }}">View page source</a> {% elif show_source and source_url_prefix %}
{% elif show_source and has_source and sourcename %} <a href="{{ source_url_prefix }}{{ pagename }}{{ source_suffix }}">View page source</a>
<a href="{{ pathto('_sources/' + sourcename, true)|e }}" rel="nofollow"> View page source</a> {% elif show_source and has_source and sourcename %}
<a href="{{ pathto('_sources/' + sourcename, true)|e }}" rel="nofollow"> View page source</a>
{% endif %}
{% endif %} {% endif %}
</li> </li>
</ul> </ul>

View File

@ -2,10 +2,10 @@
{% if next or prev %} {% if next or prev %}
<div class="rst-footer-buttons" role="navigation" aria-label="footer navigation"> <div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
{% if next %} {% if next %}
<a href="{{ next.link|e }}" class="btn btn-neutral float-right" title="{{ next.title|striptags|e }}">Next <span class="fa fa-arrow-circle-right"></span></a> <a href="{{ next.link|e }}" class="btn btn-neutral float-right" title="{{ next.title|striptags|e }}" accesskey="n">Next <span class="fa fa-arrow-circle-right"></span></a>
{% endif %} {% endif %}
{% if prev %} {% if prev %}
<a href="{{ prev.link|e }}" class="btn btn-neutral" title="{{ prev.title|striptags|e }}"><span class="fa fa-arrow-circle-left"></span> Previous</a> <a href="{{ prev.link|e }}" class="btn btn-neutral" title="{{ prev.title|striptags|e }}" accesskey="p"><span class="fa fa-arrow-circle-left"></span> Previous</a>
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}

View File

@ -107,7 +107,7 @@
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation"> <div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
{% block menu %} {% block menu %}
{% set toctree = toctree(maxdepth=2, collapse=False, includehidden=True) %} {% set toctree = toctree(maxdepth=4, collapse=False, includehidden=True) %}
{% if toctree %} {% if toctree %}
{{ toctree }} {{ toctree }}
{% else %} {% else %}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,50 +1,113 @@
$( document ).ready(function() { function toggleCurrent (elem) {
var parent_li = elem.closest('li');
parent_li.siblings('li.current').removeClass('current');
parent_li.siblings().find('li.current').removeClass('current');
parent_li.find('> ul li.current').removeClass('current');
parent_li.toggleClass('current');
}
$(document).ready(function() {
// Shift nav in mobile when clicking the menu. // Shift nav in mobile when clicking the menu.
$(document).on('click', "[data-toggle='wy-nav-top']", function() { $(document).on('click', "[data-toggle='wy-nav-top']", function() {
$("[data-toggle='wy-nav-shift']").toggleClass("shift"); $("[data-toggle='wy-nav-shift']").toggleClass("shift");
$("[data-toggle='rst-versions']").toggleClass("shift"); $("[data-toggle='rst-versions']").toggleClass("shift");
}); });
// Close menu when you click a link. // Nav menu link click operations
$(document).on('click', ".wy-menu-vertical .current ul li a", function() { $(document).on('click', ".wy-menu-vertical .current ul li a", function() {
$("[data-toggle='wy-nav-shift']").removeClass("shift"); var target = $(this);
$("[data-toggle='rst-versions']").toggleClass("shift"); // Close menu when you click a link.
$("[data-toggle='wy-nav-shift']").removeClass("shift");
$("[data-toggle='rst-versions']").toggleClass("shift");
// Handle dynamic display of l3 and l4 nav lists
toggleCurrent(target);
if (typeof(window.SphinxRtdTheme) != 'undefined') {
window.SphinxRtdTheme.StickyNav.hashChange();
}
}); });
$(document).on('click', "[data-toggle='rst-current-version']", function() { $(document).on('click', "[data-toggle='rst-current-version']", function() {
$("[data-toggle='rst-versions']").toggleClass("shift-up"); $("[data-toggle='rst-versions']").toggleClass("shift-up");
}); });
// Make tables responsive // Make tables responsive
$("table.docutils:not(.field-list)").wrap("<div class='wy-table-responsive'></div>"); $("table.docutils:not(.field-list)").wrap("<div class='wy-table-responsive'></div>");
// Add expand links to all parents of nested ul
$('.wy-menu-vertical ul').siblings('a').each(function () {
var link = $(this);
expand = $('<span class="toctree-expand"></span>');
expand.on('click', function (ev) {
toggleCurrent(link);
ev.stopPropagation();
return false;
});
link.prepend(expand);
});
}); });
// Sphinx theme state
window.SphinxRtdTheme = (function (jquery) { window.SphinxRtdTheme = (function (jquery) {
var stickyNav = (function () { var stickyNav = (function () {
var navBar, var navBar,
win, win,
stickyNavCssClass = 'stickynav',
winScroll = false, winScroll = false,
linkScroll = false,
winPosition = 0,
enable = function () { enable = function () {
navBar.addClass(stickyNavCssClass); init();
win.on('scroll', function() { // set flag on scroll event reset();
winScroll = true; win.on('hashchange', reset);
// Set scrolling
win.on('scroll', function () {
if (!linkScroll) {
winScroll = true;
}
}); });
// use setInterval to only handle a subset of scroll events so we don't kill scroll performance setInterval(function () {
setInterval(function() {
if (winScroll) { if (winScroll) {
winScroll = false; winScroll = false;
navBar.scrollTop(win.scrollTop()); var newWinPosition = win.scrollTop(),
navPosition = navBar.scrollTop(),
newNavPosition = navPosition + (newWinPosition - winPosition);
navBar.scrollTop(newNavPosition);
winPosition = newWinPosition;
} }
}, 100); }, 25);
}, },
init = function () { init = function () {
navBar = jquery('nav.wy-nav-side:first'); navBar = jquery('nav.wy-nav-side:first');
win = jquery(window); win = jquery(window);
},
reset = function () {
// Get anchor from URL and open up nested nav
var anchor = encodeURI(window.location.hash);
if (anchor) {
try {
var link = $('.wy-menu-vertical')
.find('[href="' + anchor + '"]');
$('.wy-menu-vertical li.toctree-l1 li.current')
.removeClass('current');
link.closest('li.toctree-l2').addClass('current');
link.closest('li.toctree-l3').addClass('current');
link.closest('li.toctree-l4').addClass('current');
}
catch (err) {
console.log("Error expanding nav for anchor", err);
}
}
},
hashChange = function () {
linkScroll = true;
win.one('hashchange', function () {
linkScroll = false;
});
}; };
jquery(init); jquery(init);
return { return {
enable : enable enable: enable,
hashChange: hashChange
}; };
}()); }());
return { return {
StickyNav : stickyNav StickyNav: stickyNav
}; };
}($)); }($));

View File

@ -8,7 +8,7 @@ _nghttpd()
_get_comp_words_by_ref cur prev _get_comp_words_by_ref cur prev
case $cur in case $cur in
-*) -*)
COMPREPLY=( $( compgen -W '--error-gzip --push --header-table-size --trailer --htdocs --address --padding --verbose --version --help --hexdump --daemon --verify-client --workers --no-tls --color --early-response --dh-param-file ' -- "$cur" ) ) COMPREPLY=( $( compgen -W '--error-gzip --push --header-table-size --trailer --htdocs --address --padding --verbose --version --help --hexdump --dh-param-file --daemon --verify-client --workers --no-tls --color --early-response --max-concurrent-streams ' -- "$cur" ) )
;; ;;
*) *)
_filedir _filedir

View File

@ -8,7 +8,7 @@ _nghttpx()
_get_comp_words_by_ref cur prev _get_comp_words_by_ref cur prev
case $cur in case $cur in
-*) -*)
COMPREPLY=( $( compgen -W '--frontend-http2-connection-window-bits --worker-read-rate --frontend-no-tls --frontend-http2-dump-request-header --daemon --write-rate --altsvc --frontend-http2-dump-response-header --backend-http1-connections-per-frontend --tls-ticket-key-file --ciphers --verify-client-cacert --backend-keep-alive-timeout --strip-incoming-x-forwarded-for --errorlog-file --private-key-passwd-file --version --backlog --backend-http-proxy-uri --add-response-header --backend-write-timeout --backend-request-buffer --add-x-forwarded-for --write-burst --backend-http2-connection-window-bits --insecure --rlimit-nofile --backend-http2-window-bits --tls-proto-list --no-location-rewrite --padding --conf --accesslog-syslog --backend-http2-connections-per-worker --http2-max-concurrent-streams --client-proxy --worker-frontend-connections --ocsp-update-interval --cacert --frontend-read-timeout --worker-write-burst --npn-list --syslog-facility --backend-http1-connections-per-host --no-server-push --client --http2-bridge --fetch-ocsp-response-file --no-via --user --stream-write-timeout --no-ocsp --backend-response-buffer --http2-no-cookie-crumbling --backend-read-timeout --stream-read-timeout --workers --worker-read-burst --dh-param-file --errorlog-syslog --frontend --accesslog-file --http2-proxy --frontend-http2-read-timeout --accesslog-format --frontend-http2-window-bits --backend-no-tls --client-private-key-file --pid-file --client-cert-file --no-host-rewrite --log-level --worker-write-rate --help --backend-tls-sni-field --subcert --frontend-frame-debug --frontend-write-timeout --verify-client --read-rate --read-burst --backend-ipv4 --listener-disable-timeout --backend-ipv6 --backend ' -- "$cur" ) ) COMPREPLY=( $( compgen -W '--worker-read-rate --frontend-no-tls --frontend-http2-dump-response-header --backend-http1-connections-per-frontend --tls-ticket-key-file --verify-client-cacert --backend-request-buffer --backend-http2-connection-window-bits --conf --worker-write-burst --npn-list --fetch-ocsp-response-file --stream-read-timeout --accesslog-syslog --frontend-http2-read-timeout --listener-disable-timeout --frontend-http2-connection-window-bits --ciphers --strip-incoming-x-forwarded-for --daemon --backend-keep-alive-timeout --backend-http-proxy-uri --backend-http1-connections-per-host --rlimit-nofile --no-via --ocsp-update-interval --backend-write-timeout --client --http2-no-cookie-crumbling --worker-read-burst --client-proxy --http2-bridge --accesslog-format --errorlog-syslog --errorlog-file --http2-max-concurrent-streams --frontend-write-timeout --read-burst --backend-ipv4 --backend-ipv6 --backend --insecure --log-level --tls-proto-list --backend-http2-connections-per-worker --dh-param-file --worker-frontend-connections --header-field-buffer --no-server-push --no-location-rewrite --no-ocsp --backend-response-buffer --workers --frontend-http2-window-bits --no-host-rewrite --worker-write-rate --backend-tls-sni-field --subcert --help --frontend-frame-debug --pid-file --frontend-http2-dump-request-header --private-key-passwd-file --write-rate --altsvc --user --add-x-forwarded-for --syslog-facility --frontend-read-timeout --backlog --write-burst --backend-http2-window-bits --padding --stream-write-timeout --cacert --version --verify-client --backend-read-timeout --frontend --accesslog-file --http2-proxy --max-header-fields --backend-no-tls --client-private-key-file --client-cert-file --add-response-header --read-rate ' -- "$cur" ) )
;; ;;
*) *)
_filedir _filedir

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "H2LOAD" "1" "April 27, 2015" "0.7.13" "nghttp2" .TH "H2LOAD" "1" "May 08, 2015" "0.7.14" "nghttp2"
.SH NAME .SH NAME
h2load \- HTTP/2 benchmarking tool h2load \- HTTP/2 benchmarking tool
. .
@ -205,12 +205,55 @@ The maximum time taken for request and response.
The mean time taken for request and response. The mean time taken for request and response.
.TP .TP
.B sd .B sd
The standard deviation of the time for request and response. The standard deviation of the time taken for request and response.
.TP .TP
.B +/\- sd .B +/\- sd
The fraction of the number of requests within standard deviation The fraction of the number of requests within standard deviation
range (mean +/\- sd) against total number of successful requests. range (mean +/\- sd) against total number of successful requests.
.UNINDENT .UNINDENT
.TP
.B time for connect
.INDENT 7.0
.TP
.B min
The minimum time taken to connect to a server.
.TP
.B max
The maximum time taken to connect to a server.
.TP
.B mean
The mean time taken to connect to a server.
.TP
.B sd
The standard deviation of the time taken to connect to a server.
.TP
.B +/\- sd
The fraction of the number of connections within standard
deviation range (mean +/\- sd) against total number of successful
connections.
.UNINDENT
.TP
.B time for 1st byte (of (decrypted in case of TLS) application data)
.INDENT 7.0
.TP
.B min
The minimum time taken to get 1st byte from a server.
.TP
.B max
The maximum time taken to get 1st byte from a server.
.TP
.B mean
The mean time taken to get 1st byte from a server.
.TP
.B sd
The standard deviation of the time taken to get 1st byte from a
server.
.TP
.B +/\- sd
The fraction of the number of connections within standard
deviation range (mean +/\- sd) against total number of successful
connections.
.UNINDENT
.UNINDENT .UNINDENT
.SH FLOW CONTROL .SH FLOW CONTROL
.sp .sp

View File

@ -153,11 +153,40 @@ time for request
mean mean
The mean time taken for request and response. The mean time taken for request and response.
sd sd
The standard deviation of the time for request and response. The standard deviation of the time taken for request and response.
+/- sd +/- sd
The fraction of the number of requests within standard deviation The fraction of the number of requests within standard deviation
range (mean +/- sd) against total number of successful requests. range (mean +/- sd) against total number of successful requests.
time for connect
min
The minimum time taken to connect to a server.
max
The maximum time taken to connect to a server.
mean
The mean time taken to connect to a server.
sd
The standard deviation of the time taken to connect to a server.
+/- sd
The fraction of the number of connections within standard
deviation range (mean +/- sd) against total number of successful
connections.
time for 1st byte (of (decrypted in case of TLS) application data)
min
The minimum time taken to get 1st byte from a server.
max
The maximum time taken to get 1st byte from a server.
mean
The mean time taken to get 1st byte from a server.
sd
The standard deviation of the time taken to get 1st byte from a
server.
+/- sd
The fraction of the number of connections within standard
deviation range (mean +/- sd) against total number of successful
connections.
FLOW CONTROL FLOW CONTROL
------------ ------------

View File

@ -44,11 +44,40 @@ time for request
mean mean
The mean time taken for request and response. The mean time taken for request and response.
sd sd
The standard deviation of the time for request and response. The standard deviation of the time taken for request and response.
+/- sd +/- sd
The fraction of the number of requests within standard deviation The fraction of the number of requests within standard deviation
range (mean +/- sd) against total number of successful requests. range (mean +/- sd) against total number of successful requests.
time for connect
min
The minimum time taken to connect to a server.
max
The maximum time taken to connect to a server.
mean
The mean time taken to connect to a server.
sd
The standard deviation of the time taken to connect to a server.
+/- sd
The fraction of the number of connections within standard
deviation range (mean +/- sd) against total number of successful
connections.
time for 1st byte (of (decrypted in case of TLS) application data)
min
The minimum time taken to get 1st byte from a server.
max
The maximum time taken to get 1st byte from a server.
mean
The mean time taken to get 1st byte from a server.
sd
The standard deviation of the time taken to get 1st byte from a
server.
+/- sd
The fraction of the number of connections within standard
deviation range (mean +/- sd) against total number of successful
connections.
FLOW CONTROL FLOW CONTROL
------------ ------------

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "NGHTTP" "1" "April 27, 2015" "0.7.13" "nghttp2" .TH "NGHTTP" "1" "May 08, 2015" "0.7.14" "nghttp2"
.SH NAME .SH NAME
nghttp \- HTTP/2 experimental client nghttp \- HTTP/2 experimental client
. .
@ -64,8 +64,9 @@ yet.
.UNINDENT .UNINDENT
.INDENT 0.0 .INDENT 0.0
.TP .TP
.B \-t, \-\-timeout=<SEC> .B \-t, \-\-timeout=<DURATION>
Timeout each request after <SEC> seconds. Timeout each request after <DURATION>. Set 0 to disable
timeout.
.UNINDENT .UNINDENT
.INDENT 0.0 .INDENT 0.0
.TP .TP
@ -215,6 +216,11 @@ Display this help and exit.
.sp .sp
The <SIZE> argument is an integer and an optional unit (e.g., 10K is The <SIZE> argument is an integer and an optional unit (e.g., 10K is
10 * 1024). Units are K, M and G (powers of 1024). 10 * 1024). Units are K, M and G (powers of 1024).
.sp
The <DURATION> argument is an integer and an optional unit (e.g., 1s
is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms
(hours, minutes, seconds and milliseconds, respectively). If a unit
is omitted, a second is used as unit.
.SH DEPENDENCY BASED PRIORITY .SH DEPENDENCY BASED PRIORITY
.sp .sp
nghttp sends priority hints to server by default unless nghttp sends priority hints to server by default unless

View File

@ -38,9 +38,10 @@ OPTIONS
'index.html' is used as a filename. Not implemented 'index.html' is used as a filename. Not implemented
yet. yet.
.. option:: -t, --timeout=<SEC> .. option:: -t, --timeout=<DURATION>
Timeout each request after <SEC> seconds. Timeout each request after <DURATION>. Set 0 to disable
timeout.
.. option:: -w, --window-bits=<N> .. option:: -w, --window-bits=<N>
@ -168,6 +169,11 @@ OPTIONS
The <SIZE> argument is an integer and an optional unit (e.g., 10K is The <SIZE> argument is an integer and an optional unit (e.g., 10K is
10 * 1024). Units are K, M and G (powers of 1024). 10 * 1024). Units are K, M and G (powers of 1024).
The <DURATION> argument is an integer and an optional unit (e.g., 1s
is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms
(hours, minutes, seconds and milliseconds, respectively). If a unit
is omitted, a second is used as unit.
DEPENDENCY BASED PRIORITY DEPENDENCY BASED PRIORITY
------------------------- -------------------------

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "NGHTTPD" "1" "April 27, 2015" "0.7.13" "nghttp2" .TH "NGHTTPD" "1" "May 08, 2015" "0.7.14" "nghttp2"
.SH NAME .SH NAME
nghttpd \- HTTP/2 experimental server nghttpd \- HTTP/2 experimental server
. .
@ -119,6 +119,14 @@ Specify 0 to disable padding.
.UNINDENT .UNINDENT
.INDENT 0.0 .INDENT 0.0
.TP .TP
.B \-m, \-\-max\-concurrent\-streams=<N>
Set the maximum number of the concurrent streams in one
HTTP/2 session.
.sp
Default: \fB100\fP
.UNINDENT
.INDENT 0.0
.TP
.B \-n, \-\-workers=<N> .B \-n, \-\-workers=<N>
Set the number of worker threads. Set the number of worker threads.
.sp .sp

View File

@ -85,6 +85,13 @@ OPTIONS
Add at most <N> bytes to a frame payload as padding. Add at most <N> bytes to a frame payload as padding.
Specify 0 to disable padding. Specify 0 to disable padding.
.. option:: -m, --max-concurrent-streams=<N>
Set the maximum number of the concurrent streams in one
HTTP/2 session.
Default: ``100``
.. option:: -n, --workers=<N> .. option:: -n, --workers=<N>
Set the number of worker threads. Set the number of worker threads.

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "NGHTTPX" "1" "April 27, 2015" "0.7.13" "nghttp2" .TH "NGHTTPX" "1" "May 08, 2015" "0.7.14" "nghttp2"
.SH NAME .SH NAME
nghttpx \- HTTP/2 experimental proxy nghttpx \- HTTP/2 experimental proxy
. .
@ -704,6 +704,23 @@ won\(aqt replace anything already set. This option can be
used several times to specify multiple header fields. used several times to specify multiple header fields.
Example: \fI\%\-\-add\-response\-header\fP="foo: bar" Example: \fI\%\-\-add\-response\-header\fP="foo: bar"
.UNINDENT .UNINDENT
.INDENT 0.0
.TP
.B \-\-header\-field\-buffer=<SIZE>
Set maximum buffer size for incoming HTTP header field
list. This is the sum of header name and value in
bytes.
.sp
Default: \fB64K\fP
.UNINDENT
.INDENT 0.0
.TP
.B \-\-max\-header\-fields=<N>
Set maximum number of incoming HTTP header fields, which
appear in one request or response header field list.
.sp
Default: \fB100\fP
.UNINDENT
.SS Debug .SS Debug
.INDENT 0.0 .INDENT 0.0
.TP .TP

View File

@ -619,6 +619,21 @@ HTTP
used several times to specify multiple header fields. used several times to specify multiple header fields.
Example: :option:`--add-response-header`\="foo: bar" Example: :option:`--add-response-header`\="foo: bar"
.. option:: --header-field-buffer=<SIZE>
Set maximum buffer size for incoming HTTP header field
list. This is the sum of header name and value in
bytes.
Default: ``64K``
.. option:: --max-header-fields=<N>
Set maximum number of incoming HTTP header fields, which
appear in one request or response header field list.
Default: ``100``
Debug Debug
~~~~~ ~~~~~

View File

@ -189,7 +189,7 @@ typedef enum {
NGHTTP2_TOKEN__METHOD, NGHTTP2_TOKEN__METHOD,
NGHTTP2_TOKEN__PATH, NGHTTP2_TOKEN__PATH,
NGHTTP2_TOKEN__SCHEME, NGHTTP2_TOKEN__SCHEME,
NGHTTP2_TOKEN_HOST, NGHTTP2_TOKEN_HOST
} nghttp2_token; } nghttp2_token;
/* Inspired by h2o header lookup. https://github.com/h2o/h2o */ /* Inspired by h2o header lookup. https://github.com/h2o/h2o */

View File

@ -246,6 +246,72 @@ func TestH1H1RequestTrailer(t *testing.T) {
} }
} }
// TestH1H1HeaderFieldBufferPath tests that request with request path
// larger than configured buffer size is rejected.
func TestH1H1HeaderFieldBufferPath(t *testing.T) {
// The value 100 is chosen so that sum of header fields bytes
// does not exceed it. We use > 100 bytes URI to exceed this
// limit.
st := newServerTester([]string{"--header-field-buffer=100"}, t, func(w http.ResponseWriter, r *http.Request) {
t.Fatal("execution path should not be here")
})
defer st.Close()
res, err := st.http1(requestParam{
name: "TestH1H1HeaderFieldBufferPath",
path: "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
})
if err != nil {
t.Fatalf("Error st.http1() = %v", err)
}
if got, want := res.status, 431; got != want {
t.Errorf("status: %v; want %v", got, want)
}
}
// TestH1H1HeaderFieldBuffer tests that request with header fields
// larger than configured buffer size is rejected.
func TestH1H1HeaderFieldBuffer(t *testing.T) {
st := newServerTester([]string{"--header-field-buffer=10"}, t, func(w http.ResponseWriter, r *http.Request) {
t.Fatal("execution path should not be here")
})
defer st.Close()
res, err := st.http1(requestParam{
name: "TestH1H1HeaderFieldBuffer",
})
if err != nil {
t.Fatalf("Error st.http1() = %v", err)
}
if got, want := res.status, 431; got != want {
t.Errorf("status: %v; want %v", got, want)
}
}
// TestH1H1HeaderFields tests that request with header fields more
// than configured number is rejected.
func TestH1H1HeaderFields(t *testing.T) {
st := newServerTester([]string{"--max-header-fields=1"}, t, func(w http.ResponseWriter, r *http.Request) {
t.Fatal("execution path should not be here")
})
defer st.Close()
res, err := st.http1(requestParam{
name: "TestH1H1HeaderFields",
header: []hpack.HeaderField{
// Add extra header field to ensure that
// header field limit exceeds
pair("Connection", "close"),
},
})
if err != nil {
t.Fatalf("Error st.http1() = %v", err)
}
if got, want := res.status, 431; got != want {
t.Errorf("status: %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) {

View File

@ -558,6 +558,46 @@ func TestH2H1RequestTrailer(t *testing.T) {
} }
} }
// TestH2H1HeaderFieldBuffer tests that request with header fields
// larger than configured buffer size is rejected.
func TestH2H1HeaderFieldBuffer(t *testing.T) {
st := newServerTester([]string{"--header-field-buffer=10"}, t, func(w http.ResponseWriter, r *http.Request) {
t.Fatal("execution path should not be here")
})
defer st.Close()
res, err := st.http2(requestParam{
name: "TestH2H1HeaderFieldBuffer",
})
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.status, 431; got != want {
t.Errorf("status: %v; want %v", got, want)
}
}
// TestH2H1HeaderFields tests that request with header fields more
// than configured number is rejected.
func TestH2H1HeaderFields(t *testing.T) {
st := newServerTester([]string{"--max-header-fields=1"}, t, func(w http.ResponseWriter, r *http.Request) {
t.Fatal("execution path should not be here")
})
defer st.Close()
res, err := st.http2(requestParam{
name: "TestH2H1HeaderFields",
// we have at least 4 pseudo-header fields sent, and
// that ensures that buffer limit exceeds.
})
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.status, 431; got != want {
t.Errorf("status: %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) {})

View File

@ -170,6 +170,46 @@ func TestS3H1NoVia(t *testing.T) {
} }
} }
// TestS3H1HeaderFieldBuffer tests that request with header fields
// larger than configured buffer size is rejected.
func TestS3H1HeaderFieldBuffer(t *testing.T) {
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--header-field-buffer=10"}, t, func(w http.ResponseWriter, r *http.Request) {
t.Fatal("execution path should not be here")
})
defer st.Close()
res, err := st.spdy(requestParam{
name: "TestS3H1HeaderFieldBuffer",
})
if err != nil {
t.Fatalf("Error st.spdy() = %v", err)
}
if got, want := res.spdyRstErrCode, spdy.InternalError; got != want {
t.Errorf("res.spdyRstErrCode: %v; want %v", got, want)
}
}
// TestS3H1HeaderFields tests that request with header fields more
// than configured number is rejected.
func TestS3H1HeaderFields(t *testing.T) {
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--max-header-fields=1"}, t, func(w http.ResponseWriter, r *http.Request) {
t.Fatal("execution path should not be here")
})
defer st.Close()
res, err := st.spdy(requestParam{
name: "TestS3H1HeaderFields",
// we have at least 5 pseudo-header fields sent, and
// that ensures that buffer limit exceeds.
})
if err != nil {
t.Fatalf("Error st.spdy() = %v", err)
}
if got, want := res.spdyRstErrCode, spdy.InternalError; got != want {
t.Errorf("res.spdyRstErrCode: %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) {

View File

@ -297,7 +297,19 @@ func (st *serverTester) http1(rp requestParam) (*serverResponse, error) {
body = cbr body = cbr
} }
} }
req, err := http.NewRequest(method, st.url, body)
reqURL := st.url
if rp.path != "" {
u, err := url.Parse(st.url)
if err != nil {
st.t.Fatalf("Error parsing URL from st.url %v: %v", st.url, err)
}
u.Path = rp.path
reqURL = u.String()
}
req, err := http.NewRequest(method, reqURL, body)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -2895,13 +2895,13 @@ nghttp2_priority_spec_check_default(const nghttp2_priority_spec *pri_spec);
* If |data_prd| is not ``NULL``, it provides data which will be sent * If |data_prd| is not ``NULL``, it provides data which will be sent
* in subsequent DATA frames. In this case, a method that allows * in subsequent DATA frames. In this case, a method that allows
* request message bodies * request message bodies
* (http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9) must * (https://tools.ietf.org/html/rfc7231#section-4) must be specified
* be specified with ``:method`` key in |nva| (e.g. ``POST``). This * with ``:method`` key in |nva| (e.g. ``POST``). This function does
* function does not take ownership of the |data_prd|. The function * not take ownership of the |data_prd|. The function copies the
* copies the members of the |data_prd|. If |data_prd| is ``NULL``, * members of the |data_prd|. If |data_prd| is ``NULL``, HEADERS have
* HEADERS have END_STREAM set. The |stream_user_data| is data * END_STREAM set. The |stream_user_data| is data associated to the
* associated to the stream opened by this request and can be an * stream opened by this request and can be an arbitrary pointer,
* arbitrary pointer, which can be retrieved later by * which can be retrieved later by
* `nghttp2_session_get_stream_user_data()`. * `nghttp2_session_get_stream_user_data()`.
* *
* This function returns assigned stream ID if it succeeds, or one of * This function returns assigned stream ID if it succeeds, or one of
@ -3492,7 +3492,10 @@ NGHTTP2_EXTERN int nghttp2_nv_compare_name(const nghttp2_nv *lhs,
* { * {
* int rv; * int rv;
* rv = nghttp2_select_next_protocol(out, outlen, in, inlen); * rv = nghttp2_select_next_protocol(out, outlen, in, inlen);
* if(rv == 1) { * if (rv == -1) {
* return SSL_TLSEXT_ERR_NOACK;
* }
* if (rv == 1) {
* ((MyType*)arg)->http2_selected = 1; * ((MyType*)arg)->http2_selected = 1;
* } * }
* return SSL_TLSEXT_ERR_OK; * return SSL_TLSEXT_ERR_OK;

View File

@ -1157,15 +1157,11 @@ static nghttp2_hd_entry *add_hd_table_incremental(nghttp2_hd_context *context,
} }
static int name_eq(const nghttp2_nv *a, const nghttp2_nv *b) { static int name_eq(const nghttp2_nv *a, const nghttp2_nv *b) {
return a->namelen == b->namelen && return a->namelen == b->namelen && memeq(a->name, b->name, a->namelen);
a->name[a->namelen - 1] == b->name[a->namelen - 1] &&
memeq(a->name, b->name, a->namelen);
} }
static int value_eq(const nghttp2_nv *a, const nghttp2_nv *b) { static int value_eq(const nghttp2_nv *a, const nghttp2_nv *b) {
return a->valuelen == b->valuelen && return a->valuelen == b->valuelen && memeq(a->value, b->value, a->valuelen);
a->value[a->valuelen - 1] == b->value[a->valuelen - 1] &&
memeq(a->value, b->value, a->valuelen);
} }
typedef struct { typedef struct {
@ -1733,7 +1729,9 @@ static int hd_inflate_remove_bufs(nghttp2_hd_inflater *inflater, nghttp2_nv *nv,
static int hd_inflate_remove_bufs_with_name(nghttp2_hd_inflater *inflater, static int hd_inflate_remove_bufs_with_name(nghttp2_hd_inflater *inflater,
nghttp2_nv *nv, nghttp2_nv *nv,
nghttp2_hd_entry *ent_name) { nghttp2_hd_entry *ent_name) {
#ifndef NDEBUG
size_t rv; size_t rv;
#endif
size_t buflen; size_t buflen;
uint8_t *buf; uint8_t *buf;
nghttp2_mem *mem; nghttp2_mem *mem;
@ -1751,8 +1749,11 @@ static int hd_inflate_remove_bufs_with_name(nghttp2_hd_inflater *inflater,
/* Copy including terminal NULL */ /* Copy including terminal NULL */
memcpy(buf, ent_name->nv.name, ent_name->nv.namelen + 1); memcpy(buf, ent_name->nv.name, ent_name->nv.namelen + 1);
rv = nghttp2_bufs_remove_copy(&inflater->nvbufs, #ifndef NDEBUG
buf + ent_name->nv.namelen + 1); rv =
#endif
nghttp2_bufs_remove_copy(&inflater->nvbufs,
buf + ent_name->nv.namelen + 1);
assert(ent_name->nv.namelen + 1 + rv == buflen); assert(ent_name->nv.namelen + 1 + rv == buflen);
nghttp2_bufs_reset(&inflater->nvbufs); nghttp2_bufs_reset(&inflater->nvbufs);

View File

@ -109,7 +109,7 @@ typedef enum {
NGHTTP2_TOKEN_CONNECTION, NGHTTP2_TOKEN_CONNECTION,
NGHTTP2_TOKEN_KEEP_ALIVE, NGHTTP2_TOKEN_KEEP_ALIVE,
NGHTTP2_TOKEN_PROXY_CONNECTION, NGHTTP2_TOKEN_PROXY_CONNECTION,
NGHTTP2_TOKEN_UPGRADE, NGHTTP2_TOKEN_UPGRADE
} nghttp2_token; } nghttp2_token;
typedef enum { typedef enum {

View File

@ -58,7 +58,7 @@ typedef enum {
*/ */
NGHTTP2_OPT_PEER_MAX_CONCURRENT_STREAMS = 1 << 1, NGHTTP2_OPT_PEER_MAX_CONCURRENT_STREAMS = 1 << 1,
NGHTTP2_OPT_NO_RECV_CLIENT_MAGIC = 1 << 2, NGHTTP2_OPT_NO_RECV_CLIENT_MAGIC = 1 << 2,
NGHTTP2_OPT_NO_HTTP_MESSAGING = 1 << 3, NGHTTP2_OPT_NO_HTTP_MESSAGING = 1 << 3
} nghttp2_option_flag; } nghttp2_option_flag;
/** /**

View File

@ -81,7 +81,7 @@ typedef enum {
/* indicates that this GOAWAY is just a notification for graceful /* indicates that this GOAWAY is just a notification for graceful
shutdown. No nghttp2_session.goaway_flags should be updated on shutdown. No nghttp2_session.goaway_flags should be updated on
the reaction to this frame. */ the reaction to this frame. */
NGHTTP2_GOAWAY_AUX_SHUTDOWN_NOTICE = 0x2, NGHTTP2_GOAWAY_AUX_SHUTDOWN_NOTICE = 0x2
} nghttp2_goaway_aux_flag; } nghttp2_goaway_aux_flag;
/* struct used for GOAWAY frame */ /* struct used for GOAWAY frame */

View File

@ -47,7 +47,7 @@
typedef enum { typedef enum {
NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE = 1 << 0, NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE = 1 << 0,
NGHTTP2_OPTMASK_NO_RECV_CLIENT_MAGIC = 1 << 1, NGHTTP2_OPTMASK_NO_RECV_CLIENT_MAGIC = 1 << 1,
NGHTTP2_OPTMASK_NO_HTTP_MESSAGING = 1 << 2, NGHTTP2_OPTMASK_NO_HTTP_MESSAGING = 1 << 2
} nghttp2_optmask; } nghttp2_optmask;
typedef enum { typedef enum {
@ -85,7 +85,7 @@ typedef enum {
NGHTTP2_IB_READ_PAD_DATA, NGHTTP2_IB_READ_PAD_DATA,
NGHTTP2_IB_READ_DATA, NGHTTP2_IB_READ_DATA,
NGHTTP2_IB_IGN_DATA, NGHTTP2_IB_IGN_DATA,
NGHTTP2_IB_IGN_ALL, NGHTTP2_IB_IGN_ALL
} nghttp2_inbound_state; } nghttp2_inbound_state;
#define NGHTTP2_INBOUND_NUM_IV 7 #define NGHTTP2_INBOUND_NUM_IV 7
@ -137,7 +137,7 @@ typedef enum {
/* Flag means GOAWAY was sent */ /* Flag means GOAWAY was sent */
NGHTTP2_GOAWAY_SENT = 0x4, NGHTTP2_GOAWAY_SENT = 0x4,
/* Flag means GOAWAY was received */ /* Flag means GOAWAY was received */
NGHTTP2_GOAWAY_RECV = 0x8, NGHTTP2_GOAWAY_RECV = 0x8
} nghttp2_goaway_flag; } nghttp2_goaway_flag;
struct nghttp2_session { struct nghttp2_session {

View File

@ -133,7 +133,7 @@ typedef enum {
/* "http" or "https" scheme */ /* "http" or "https" scheme */
NGHTTP2_HTTP_FLAG_SCHEME_HTTP = 1 << 12, NGHTTP2_HTTP_FLAG_SCHEME_HTTP = 1 << 12,
/* set if final response is expected */ /* set if final response is expected */
NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE = 1 << 13, NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE = 1 << 13
} nghttp2_http_flag; } nghttp2_http_flag;
typedef enum { typedef enum {

View File

@ -88,8 +88,9 @@ template <typename Array> void append_nv(Stream *stream, const Array &nva) {
Config::Config() Config::Config()
: stream_read_timeout(60.), stream_write_timeout(60.), data_ptr(nullptr), : stream_read_timeout(60.), stream_write_timeout(60.), data_ptr(nullptr),
padding(0), num_worker(1), header_table_size(-1), port(0), verbose(false), padding(0), num_worker(1), max_concurrent_streams(100),
daemon(false), verify_client(false), no_tls(false), error_gzip(false), header_table_size(-1), port(0), verbose(false), daemon(false),
verify_client(false), no_tls(false), error_gzip(false),
early_response(false), hexdump(false) {} early_response(false), hexdump(false) {}
Config::~Config() {} Config::~Config() {}
@ -657,18 +658,21 @@ int Http2Handler::connection_made() {
int r; int r;
r = nghttp2_session_server_new(&session_, sessions_->get_callbacks(), this); r = nghttp2_session_server_new(&session_, sessions_->get_callbacks(), this);
if (r != 0) { if (r != 0) {
return r; return r;
} }
auto config = sessions_->get_config();
std::array<nghttp2_settings_entry, 4> entry; std::array<nghttp2_settings_entry, 4> entry;
size_t niv = 1; size_t niv = 1;
entry[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; entry[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
entry[0].value = 100; entry[0].value = config->max_concurrent_streams;
if (sessions_->get_config()->header_table_size >= 0) { if (config->header_table_size >= 0) {
entry[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; entry[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
entry[niv].value = sessions_->get_config()->header_table_size; entry[niv].value = config->header_table_size;
++niv; ++niv;
} }
r = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, entry.data(), niv); r = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, entry.data(), niv);

View File

@ -62,6 +62,7 @@ struct Config {
void *data_ptr; void *data_ptr;
size_t padding; size_t padding;
size_t num_worker; size_t num_worker;
size_t max_concurrent_streams;
ssize_t header_table_size; ssize_t header_table_size;
uint16_t port; uint16_t port;
bool verbose; bool verbose;

View File

@ -150,8 +150,8 @@ void readcb(struct ev_loop *loop, ev_io *w, int revents) {
Client::Client(Worker *worker, size_t req_todo) Client::Client(Worker *worker, size_t req_todo)
: worker(worker), ssl(nullptr), next_addr(config.addrs), reqidx(0), : worker(worker), ssl(nullptr), next_addr(config.addrs), reqidx(0),
state(CLIENT_IDLE), req_todo(req_todo), req_started(0), req_done(0), state(CLIENT_IDLE), first_byte_received(false), req_todo(req_todo),
fd(-1) { req_started(0), req_done(0), fd(-1) {
ev_io_init(&wev, writecb, 0, EV_WRITE); ev_io_init(&wev, writecb, 0, EV_WRITE);
ev_io_init(&rev, readcb, 0, EV_READ); ev_io_init(&rev, readcb, 0, EV_READ);
@ -165,6 +165,8 @@ int Client::do_read() { return readfn(*this); }
int Client::do_write() { return writefn(*this); } int Client::do_write() { return writefn(*this); }
int Client::connect() { int Client::connect() {
record_start_time(&worker->stats);
while (next_addr) { while (next_addr) {
auto addr = next_addr; auto addr = next_addr;
next_addr = next_addr->ai_next; next_addr = next_addr->ai_next;
@ -469,6 +471,8 @@ int Client::connection_made() {
session->on_connect(); session->on_connect();
record_connect_time(&worker->stats);
auto nreq = auto nreq =
std::min(req_todo - req_started, (size_t)config.max_concurrent_streams); std::min(req_todo - req_started, (size_t)config.max_concurrent_streams);
@ -519,6 +523,11 @@ int Client::read_clear() {
if (on_read(buf, nread) != 0) { if (on_read(buf, nread) != 0) {
return -1; return -1;
} }
if (!first_byte_received) {
first_byte_received = true;
record_ttfb(&worker->stats);
}
} }
return 0; return 0;
@ -641,6 +650,11 @@ int Client::read_tls() {
if (on_read(buf, rv) != 0) { if (on_read(buf, rv) != 0) {
return -1; return -1;
} }
if (!first_byte_received) {
first_byte_received = true;
record_ttfb(&worker->stats);
}
} }
} }
@ -691,6 +705,18 @@ void Client::record_request_time(RequestStat *req_stat) {
req_stat->request_time = std::chrono::steady_clock::now(); req_stat->request_time = std::chrono::steady_clock::now();
} }
void Client::record_start_time(Stats *stat) {
stat->start_times.push_back(std::chrono::steady_clock::now());
}
void Client::record_connect_time(Stats *stat) {
stat->connect_times.push_back(std::chrono::steady_clock::now());
}
void Client::record_ttfb(Stats *stat) {
stat->ttfbs.push_back(std::chrono::steady_clock::now());
}
void Client::signal_write() { ev_io_start(worker->loop, &wev); } void Client::signal_write() { ev_io_start(worker->loop, &wev); }
Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients, Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients,
@ -731,70 +757,99 @@ void Worker::run() {
} }
namespace { namespace {
double within_sd(const std::vector<std::unique_ptr<Worker>> &workers, // Returns percentage of number of samples within mean +/- sd.
const std::chrono::microseconds &mean, template <typename Duration>
const std::chrono::microseconds &sd, size_t n) { double within_sd(const std::vector<Duration> &samples, const Duration &mean,
auto upper = mean.count() + sd.count(); const Duration &sd) {
auto lower = mean.count() - sd.count(); if (samples.size() == 0) {
size_t m = 0; return 0.0;
for (const auto &w : workers) {
for (const auto &req_stat : w->stats.req_stats) {
if (!req_stat.completed) {
continue;
}
auto t = std::chrono::duration_cast<std::chrono::microseconds>(
req_stat.stream_close_time - req_stat.request_time);
if (lower <= t.count() && t.count() <= upper) {
++m;
}
}
} }
return (m / static_cast<double>(n)) * 100; auto lower = mean - sd;
auto upper = mean + sd;
auto m = std::count_if(
std::begin(samples), std::end(samples),
[&lower, &upper](const Duration &t) { return lower <= t && t <= upper; });
return (m / static_cast<double>(samples.size())) * 100;
}
} // namespace
namespace {
// Computes statistics using |samples|. The min, max, mean, sd, and
// percentage of number of samples within mean +/- sd are computed.
template <typename Duration>
TimeStat<Duration> compute_time_stat(const std::vector<Duration> &samples) {
if (samples.size() == 0) {
return {Duration::zero(), Duration::zero(), Duration::zero(),
Duration::zero(), 0.0};
}
// standard deviation calculated using Rapid calculation method:
// http://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
double a = 0, q = 0;
size_t n = 0;
int64_t sum = 0;
auto res = TimeStat<Duration>{Duration::max(), Duration::min()};
for (const auto &t : samples) {
++n;
res.min = std::min(res.min, t);
res.max = std::max(res.max, t);
sum += t.count();
auto na = a + (t.count() - a) / n;
q += (t.count() - a) * (t.count() - na);
a = na;
}
res.mean = Duration(sum / n);
res.sd = Duration(static_cast<typename Duration::rep>(sqrt(q / n)));
res.within_sd = within_sd(samples, res.mean, res.sd);
return res;
} }
} // namespace } // namespace
namespace { namespace {
TimeStats TimeStats
process_time_stats(const std::vector<std::unique_ptr<Worker>> &workers) { process_time_stats(const std::vector<std::unique_ptr<Worker>> &workers) {
auto ts = TimeStats(); size_t nrequest_times = 0, nttfb_times = 0;
int64_t sum = 0; for (const auto &w : workers) {
size_t n = 0; nrequest_times += w->stats.req_stats.size();
nttfb_times += w->stats.ttfbs.size();
}
ts.time_min = std::chrono::microseconds::max(); std::vector<std::chrono::microseconds> request_times;
ts.time_max = std::chrono::microseconds::min(); request_times.reserve(nrequest_times);
ts.within_sd = 0.; std::vector<std::chrono::microseconds> connect_times, ttfb_times;
connect_times.reserve(nttfb_times);
ttfb_times.reserve(nttfb_times);
// standard deviation calculated using Rapid calculation method:
// http://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
double a = 0, q = 0;
for (const auto &w : workers) { for (const auto &w : workers) {
for (const auto &req_stat : w->stats.req_stats) { for (const auto &req_stat : w->stats.req_stats) {
if (!req_stat.completed) { if (!req_stat.completed) {
continue; continue;
} }
++n; request_times.push_back(
auto t = std::chrono::duration_cast<std::chrono::microseconds>( std::chrono::duration_cast<std::chrono::microseconds>(
req_stat.stream_close_time - req_stat.request_time); req_stat.stream_close_time - req_stat.request_time));
ts.time_min = std::min(ts.time_min, t); }
ts.time_max = std::max(ts.time_max, t);
sum += t.count();
auto na = a + (t.count() - a) / n; const auto &stat = w->stats;
q = q + (t.count() - a) * (t.count() - na); // rule out cases where we started but didn't connect or get the
a = na; // first byte (errors). We will get connect event before FFTB.
assert(stat.start_times.size() >= stat.ttfbs.size());
assert(stat.connect_times.size() >= stat.ttfbs.size());
for (size_t i = 0; i < stat.ttfbs.size(); ++i) {
connect_times.push_back(
std::chrono::duration_cast<std::chrono::microseconds>(
stat.connect_times[i] - stat.start_times[i]));
ttfb_times.push_back(
std::chrono::duration_cast<std::chrono::microseconds>(
stat.ttfbs[i] - stat.start_times[i]));
} }
} }
if (n == 0) {
ts.time_max = ts.time_min = std::chrono::microseconds::zero();
return ts;
}
ts.time_mean = std::chrono::microseconds(sum / n); return {compute_time_stat(request_times), compute_time_stat(connect_times),
ts.time_sd = std::chrono::microseconds( compute_time_stat(ttfb_times)};
static_cast<std::chrono::microseconds::rep>(sqrt(q / n)));
ts.within_sd = within_sd(workers, ts.time_mean, ts.time_sd, n);
return ts;
} }
} // namespace } // namespace
@ -1408,7 +1463,7 @@ int main(int argc, char **argv) {
} }
} }
auto time_stats = process_time_stats(workers); auto ts = process_time_stats(workers);
// Requests which have not been issued due to connection errors, are // Requests which have not been issued due to connection errors, are
// counted towards req_failed and req_error. // counted towards req_failed and req_error.
@ -1441,14 +1496,23 @@ status codes: )" << stats.status[2] << " 2xx, " << stats.status[3] << " 3xx, "
traffic: )" << stats.bytes_total << " bytes total, " << stats.bytes_head traffic: )" << stats.bytes_total << " bytes total, " << stats.bytes_head
<< " bytes headers, " << stats.bytes_body << R"( bytes data << " bytes headers, " << stats.bytes_body << R"( bytes data
min max mean sd +/- sd min max mean sd +/- sd
time for request: )" << std::setw(10) time for request: )" << std::setw(10) << util::format_duration(ts.request.min)
<< util::format_duration(time_stats.time_min) << " " << " " << std::setw(10) << util::format_duration(ts.request.max)
<< std::setw(10) << util::format_duration(time_stats.time_max) << " " << std::setw(10) << util::format_duration(ts.request.mean)
<< " " << std::setw(10) << " " << std::setw(10) << util::format_duration(ts.request.sd)
<< util::format_duration(time_stats.time_mean) << " " << std::setw(9) << util::dtos(ts.request.within_sd) << "%"
<< std::setw(10) << util::format_duration(time_stats.time_sd) << "\ntime for connect: " << std::setw(10)
<< std::setw(9) << util::dtos(time_stats.within_sd) << "%" << util::format_duration(ts.connect.min) << " " << std::setw(10)
<< std::endl; << util::format_duration(ts.connect.max) << " " << std::setw(10)
<< util::format_duration(ts.connect.mean) << " " << std::setw(10)
<< util::format_duration(ts.connect.sd) << std::setw(9)
<< util::dtos(ts.connect.within_sd) << "%"
<< "\ntime to 1st byte: " << std::setw(10)
<< util::format_duration(ts.ttfb.min) << " " << std::setw(10)
<< util::format_duration(ts.ttfb.max) << " " << std::setw(10)
<< util::format_duration(ts.ttfb.mean) << " " << std::setw(10)
<< util::format_duration(ts.ttfb.sd) << std::setw(9)
<< util::dtos(ts.ttfb.within_sd) << "%" << std::endl;
SSL_CTX_free(ssl_ctx); SSL_CTX_free(ssl_ctx);

View File

@ -94,13 +94,25 @@ struct RequestStat {
bool completed; bool completed;
}; };
struct TimeStats { template<typename Duration>
// time for request: max, min, mean and sd (standard deviation) struct TimeStat {
std::chrono::microseconds time_max, time_min, time_mean, time_sd; // min, max, mean and sd (standard deviation)
// percentage of number of requests inside mean -/+ sd Duration min, max, mean, sd;
// percentage of samples inside mean -/+ sd
double within_sd; double within_sd;
}; };
struct TimeStats {
// time for request
TimeStat<std::chrono::microseconds> request;
// time for connect
TimeStat<std::chrono::microseconds> connect;
// time to first byte (TTFB)
TimeStat<std::chrono::microseconds> ttfb;
};
enum TimeStatType { STAT_REQUEST, STAT_CONNECT, STAT_FIRST_BYTE };
struct Stats { struct Stats {
Stats(size_t req_todo); Stats(size_t req_todo);
// The total number of requests // The total number of requests
@ -132,6 +144,12 @@ struct Stats {
std::array<size_t, 6> status; std::array<size_t, 6> status;
// The statistics per request // The statistics per request
std::vector<RequestStat> req_stats; std::vector<RequestStat> req_stats;
// time connect starts
std::vector<std::chrono::steady_clock::time_point> start_times;
// time to connect
std::vector<std::chrono::steady_clock::time_point> connect_times;
// time to first byte (TTFB)
std::vector<std::chrono::steady_clock::time_point> ttfbs;
}; };
enum ClientState { CLIENT_IDLE, CLIENT_CONNECTED }; enum ClientState { CLIENT_IDLE, CLIENT_CONNECTED };
@ -171,6 +189,7 @@ struct Client {
addrinfo *next_addr; addrinfo *next_addr;
size_t reqidx; size_t reqidx;
ClientState state; ClientState state;
bool first_byte_received;
// The number of requests this client has to issue. // The number of requests this client has to issue.
size_t req_todo; size_t req_todo;
// The number of requests this client has issued so far. // The number of requests this client has issued so far.
@ -215,6 +234,9 @@ struct Client {
void on_stream_close(int32_t stream_id, bool success, RequestStat *req_stat); void on_stream_close(int32_t stream_id, bool success, RequestStat *req_stat);
void record_request_time(RequestStat *req_stat); void record_request_time(RequestStat *req_stat);
void record_start_time(Stats *stat);
void record_connect_time(Stats *stat);
void record_ttfb(Stats *stat);
void signal_write(); void signal_write();
}; };

View File

@ -25,6 +25,7 @@
#include "h2load_spdy_session.h" #include "h2load_spdy_session.h"
#include <cassert> #include <cassert>
#include <cerrno>
#include "h2load.h" #include "h2load.h"
#include "util.h" #include "util.h"

View File

@ -88,6 +88,7 @@ void print_usage(std::ostream &out) {
namespace { namespace {
void print_help(std::ostream &out) { void print_help(std::ostream &out) {
Config config;
print_usage(out); print_usage(out);
out << R"( out << R"(
<PORT> Specify listening port number. <PORT> Specify listening port number.
@ -128,6 +129,10 @@ Options:
-b, --padding=<N> -b, --padding=<N>
Add at most <N> bytes to a frame payload as padding. Add at most <N> bytes to a frame payload as padding.
Specify 0 to disable padding. Specify 0 to disable padding.
-m, --max-concurrent-streams=<N>
Set the maximum number of the concurrent streams in one
HTTP/2 session.
Default: )" << config.max_concurrent_streams << R"(
-n, --workers=<N> -n, --workers=<N>
Set the number of worker threads. Set the number of worker threads.
Default: 1 Default: 1
@ -173,6 +178,7 @@ int main(int argc, char **argv) {
{"header-table-size", required_argument, nullptr, 'c'}, {"header-table-size", required_argument, nullptr, 'c'},
{"push", required_argument, nullptr, 'p'}, {"push", required_argument, nullptr, 'p'},
{"padding", required_argument, nullptr, 'b'}, {"padding", required_argument, nullptr, 'b'},
{"max-concurrent-streams", required_argument, nullptr, 'm'},
{"workers", required_argument, nullptr, 'n'}, {"workers", required_argument, nullptr, 'n'},
{"error-gzip", no_argument, nullptr, 'e'}, {"error-gzip", no_argument, nullptr, 'e'},
{"no-tls", no_argument, &flag, 1}, {"no-tls", no_argument, &flag, 1},
@ -184,7 +190,7 @@ int main(int argc, char **argv) {
{"hexdump", no_argument, &flag, 7}, {"hexdump", no_argument, &flag, 7},
{nullptr, 0, nullptr, 0}}; {nullptr, 0, nullptr, 0}};
int option_index = 0; int option_index = 0;
int c = getopt_long(argc, argv, "DVb:c:d:ehn:p:va:", long_options, int c = getopt_long(argc, argv, "DVb:c:d:ehm:n:p:va:", long_options,
&option_index); &option_index);
char *end; char *end;
if (c == -1) { if (c == -1) {
@ -209,6 +215,16 @@ int main(int argc, char **argv) {
case 'e': case 'e':
config.error_gzip = true; config.error_gzip = true;
break; break;
case 'm': {
// max-concurrent-streams option
auto n = util::parse_uint(optarg);
if (n == -1) {
std::cerr << "-m: invalid argument: " << optarg << std::endl;
exit(EXIT_FAILURE);
}
config.max_concurrent_streams = n;
break;
}
case 'n': case 'n':
#ifdef NOTHREADS #ifdef NOTHREADS
std::cerr << "-n: WARNING: Threading disabled at build time, " std::cerr << "-n: WARNING: Threading disabled at build time, "

View File

@ -913,6 +913,8 @@ void fill_default_config() {
mod_config()->fetch_ocsp_response_file = mod_config()->fetch_ocsp_response_file =
strcopy(PKGDATADIR "/fetch-ocsp-response"); strcopy(PKGDATADIR "/fetch-ocsp-response");
mod_config()->no_ocsp = false; mod_config()->no_ocsp = false;
mod_config()->header_field_buffer = 64 * 1024;
mod_config()->max_header_fields = 100;
} }
} // namespace } // namespace
@ -1337,6 +1339,16 @@ HTTP:
won't replace anything already set. This option can be won't replace anything already set. This option can be
used several times to specify multiple header fields. used several times to specify multiple header fields.
Example: --add-response-header="foo: bar" Example: --add-response-header="foo: bar"
--header-field-buffer=<SIZE>
Set maximum buffer size for incoming HTTP header field
list. This is the sum of header name and value in
bytes.
Default: )"
<< util::utos_with_unit(get_config()->header_field_buffer) << R"(
--max-header-fields=<N>
Set maximum number of incoming HTTP header fields, which
appear in one request or response header field list.
Default: )" << get_config()->max_header_fields << R"(
Debug: Debug:
--frontend-http2-dump-request-header=<PATH> --frontend-http2-dump-request-header=<PATH>
@ -1497,6 +1509,8 @@ int main(int argc, char **argv) {
{"fetch-ocsp-response-file", required_argument, &flag, 77}, {"fetch-ocsp-response-file", required_argument, &flag, 77},
{"ocsp-update-interval", required_argument, &flag, 78}, {"ocsp-update-interval", required_argument, &flag, 78},
{"no-ocsp", no_argument, &flag, 79}, {"no-ocsp", no_argument, &flag, 79},
{"header-field-buffer", required_argument, &flag, 80},
{"max-header-fields", required_argument, &flag, 81},
{nullptr, 0, nullptr, 0}}; {nullptr, 0, nullptr, 0}};
int option_index = 0; int option_index = 0;
@ -1847,6 +1861,14 @@ int main(int argc, char **argv) {
// --no-ocsp // --no-ocsp
cmdcfgs.emplace_back(SHRPX_OPT_NO_OCSP, "yes"); cmdcfgs.emplace_back(SHRPX_OPT_NO_OCSP, "yes");
break; break;
case 80:
// --header-field-buffer
cmdcfgs.emplace_back(SHRPX_OPT_HEADER_FIELD_BUFFER, optarg);
break;
case 81:
// --max-header-fields
cmdcfgs.emplace_back(SHRPX_OPT_MAX_HEADER_FIELDS, optarg);
break;
default: default:
break; break;
} }

View File

@ -684,27 +684,75 @@ void ClientHandler::start_immediate_shutdown() {
ev_timer_start(conn_.loop, &reneg_shutdown_timer_); ev_timer_start(conn_.loop, &reneg_shutdown_timer_);
} }
namespace {
// Construct absolute request URI from |downstream|, mainly to log
// request URI for proxy request (HTTP/2 proxy or client proxy). This
// is mostly same routine found in
// HttpDownstreamConnection::push_request_headers(), but vastly
// simplified since we only care about absolute URI.
std::string construct_absolute_request_uri(Downstream *downstream) {
const char *authority = nullptr, *host = nullptr;
if (!downstream->get_request_http2_authority().empty()) {
authority = downstream->get_request_http2_authority().c_str();
}
auto h = downstream->get_request_header(http2::HD_HOST);
if (h) {
host = h->value.c_str();
}
if (!authority && !host) {
return downstream->get_request_path();
}
std::string uri;
if (downstream->get_request_http2_scheme().empty()) {
// this comes from HTTP/1 upstream without scheme. Just use http.
uri += "http://";
} else {
uri += downstream->get_request_http2_scheme();
uri += "://";
}
if (authority) {
uri += authority;
} 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;
}
} // namespace
void ClientHandler::write_accesslog(Downstream *downstream) { void ClientHandler::write_accesslog(Downstream *downstream) {
LogSpec lgsp = { upstream_accesslog(
downstream, ipaddr_.c_str(), downstream->get_request_method().c_str(), get_config()->accesslog_format,
LogSpec{
downstream, ipaddr_.c_str(), downstream->get_request_method().c_str(),
downstream->get_request_path().empty() (downstream->get_request_method() != "CONNECT" &&
? downstream->get_request_http2_authority().c_str() (get_config()->http2_proxy || get_config()->client_proxy))
: downstream->get_request_path().c_str(), ? construct_absolute_request_uri(downstream).c_str()
: downstream->get_request_path().empty()
? downstream->get_request_http2_authority().c_str()
: downstream->get_request_path().c_str(),
alpn_.c_str(), alpn_.c_str(),
std::chrono::system_clock::now(), // time_now std::chrono::system_clock::now(), // time_now
downstream->get_request_start_time(), // request_start_time downstream->get_request_start_time(), // request_start_time
std::chrono::high_resolution_clock::now(), // request_end_time std::chrono::high_resolution_clock::now(), // request_end_time
downstream->get_request_major(), downstream->get_request_minor(), downstream->get_request_major(), downstream->get_request_minor(),
downstream->get_response_http_status(), downstream->get_response_http_status(),
downstream->get_response_sent_bodylen(), port_.c_str(), downstream->get_response_sent_bodylen(), port_.c_str(),
get_config()->port, get_config()->pid, get_config()->port, get_config()->pid,
}; });
upstream_accesslog(get_config()->accesslog_format, &lgsp);
} }
void ClientHandler::write_accesslog(int major, int minor, unsigned int status, void ClientHandler::write_accesslog(int major, int minor, unsigned int status,
@ -712,20 +760,19 @@ void ClientHandler::write_accesslog(int major, int minor, unsigned int status,
auto time_now = std::chrono::system_clock::now(); auto time_now = std::chrono::system_clock::now();
auto highres_now = std::chrono::high_resolution_clock::now(); auto highres_now = std::chrono::high_resolution_clock::now();
LogSpec lgsp = { upstream_accesslog(get_config()->accesslog_format,
nullptr, ipaddr_.c_str(), LogSpec{
"-", // method nullptr, ipaddr_.c_str(),
"-", // path, "-", // method
alpn_.c_str(), time_now, "-", // path,
highres_now, // request_start_time TODO is alpn_.c_str(), time_now,
// there a better value? highres_now, // request_start_time TODO is
highres_now, // request_end_time // there a better value?
major, minor, // major, minor highres_now, // request_end_time
status, body_bytes_sent, port_.c_str(), major, minor, // major, minor
get_config()->port, get_config()->pid, status, body_bytes_sent, port_.c_str(),
}; get_config()->port, get_config()->pid,
});
upstream_accesslog(get_config()->accesslog_format, &lgsp);
} }
ClientHandler::WriteBuf *ClientHandler::get_wb() { return &wb_; } ClientHandler::WriteBuf *ClientHandler::get_wb() { return &wb_; }

View File

@ -150,6 +150,8 @@ const char SHRPX_OPT_BACKEND_HTTP2_CONNECTIONS_PER_WORKER[] =
const char SHRPX_OPT_FETCH_OCSP_RESPONSE_FILE[] = "fetch-ocsp-response-file"; const char SHRPX_OPT_FETCH_OCSP_RESPONSE_FILE[] = "fetch-ocsp-response-file";
const char SHRPX_OPT_OCSP_UPDATE_INTERVAL[] = "ocsp-update-interval"; const char SHRPX_OPT_OCSP_UPDATE_INTERVAL[] = "ocsp-update-interval";
const char SHRPX_OPT_NO_OCSP[] = "no-ocsp"; const char SHRPX_OPT_NO_OCSP[] = "no-ocsp";
const char SHRPX_OPT_HEADER_FIELD_BUFFER[] = "header-field-buffer";
const char SHRPX_OPT_MAX_HEADER_FIELDS[] = "max-header-fields";
namespace { namespace {
Config *config = nullptr; Config *config = nullptr;
@ -1212,6 +1214,15 @@ int parse_config(const char *opt, const char *optarg) {
return 0; return 0;
} }
if (util::strieq(opt, SHRPX_OPT_HEADER_FIELD_BUFFER)) {
return parse_uint_with_unit(&mod_config()->header_field_buffer, opt,
optarg);
}
if (util::strieq(opt, SHRPX_OPT_MAX_HEADER_FIELDS)) {
return parse_uint(&mod_config()->max_header_fields, opt, optarg);
}
if (util::strieq(opt, "conf")) { if (util::strieq(opt, "conf")) {
LOG(WARN) << "conf: ignored"; LOG(WARN) << "conf: ignored";

View File

@ -139,6 +139,8 @@ extern const char SHRPX_OPT_BACKEND_HTTP2_CONNECTIONS_PER_WORKER[];
extern const char SHRPX_OPT_FETCH_OCSP_RESPONSE_FILE[]; extern const char SHRPX_OPT_FETCH_OCSP_RESPONSE_FILE[];
extern const char SHRPX_OPT_OCSP_UPDATE_INTERVAL[]; extern const char SHRPX_OPT_OCSP_UPDATE_INTERVAL[];
extern const char SHRPX_OPT_NO_OCSP[]; extern const char SHRPX_OPT_NO_OCSP[];
extern const char SHRPX_OPT_HEADER_FIELD_BUFFER[];
extern const char SHRPX_OPT_MAX_HEADER_FIELDS[];
union sockaddr_union { union sockaddr_union {
sockaddr_storage storage; sockaddr_storage storage;
@ -282,6 +284,8 @@ struct Config {
size_t rlimit_nofile; size_t rlimit_nofile;
size_t downstream_request_buffer_size; size_t downstream_request_buffer_size;
size_t downstream_response_buffer_size; size_t downstream_response_buffer_size;
size_t header_field_buffer;
size_t max_header_fields;
// Bit mask to disable SSL/TLS protocol versions. This will be // Bit mask to disable SSL/TLS protocol versions. This will be
// passed to SSL_CTX_set_options(). // passed to SSL_CTX_set_options().
long int tls_proto_mask; long int tls_proto_mask;

View File

@ -1211,4 +1211,8 @@ void Downstream::detach_blocked_link(BlockedLink *l) {
blocked_link_ = nullptr; blocked_link_ = nullptr;
} }
void Downstream::add_request_headers_sum(size_t amount) {
request_headers_sum_ += amount;
}
} // namespace shrpx } // namespace shrpx

View File

@ -143,6 +143,7 @@ public:
void set_request_method(std::string method); void set_request_method(std::string method);
const std::string &get_request_method() const; const std::string &get_request_method() const;
void set_request_path(std::string path); void set_request_path(std::string path);
void add_request_headers_sum(size_t amount);
void void
set_request_start_time(std::chrono::high_resolution_clock::time_point time); set_request_start_time(std::chrono::high_resolution_clock::time_point time);
const std::chrono::high_resolution_clock::time_point & const std::chrono::high_resolution_clock::time_point &
@ -193,6 +194,10 @@ public:
// header contains invalid header field. We can safely send error // header contains invalid header field. We can safely send error
// response (502) to a client. // response (502) to a client.
MSG_BAD_HEADER, MSG_BAD_HEADER,
// header fields in HTTP/1 request exceed the configuration limit.
// This state is only transitioned from INITIAL state, and solely
// used to signal 431 status code to the client.
HTTP1_REQUEST_HEADER_TOO_LARGE,
}; };
void set_request_state(int state); void set_request_state(int state);
int get_request_state() const; int get_request_state() const;
@ -286,9 +291,6 @@ public:
// Change the priority of downstream // Change the priority of downstream
int change_priority(int32_t pri); int change_priority(int32_t pri);
// Maximum buffer size for header name/value pairs.
static constexpr size_t MAX_HEADERS_SUM = 128 * 1024;
bool get_rst_stream_after_end_stream() const; bool get_rst_stream_after_end_stream() const;
void set_rst_stream_after_end_stream(bool f); void set_rst_stream_after_end_stream(bool f);

View File

@ -733,10 +733,15 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
auto trailer = frame->headers.cat != NGHTTP2_HCAT_RESPONSE && auto trailer = frame->headers.cat != NGHTTP2_HCAT_RESPONSE &&
!downstream->get_expect_final_response(); !downstream->get_expect_final_response();
if (downstream->get_response_headers_sum() > Downstream::MAX_HEADERS_SUM) { if (downstream->get_response_headers_sum() + namelen + valuelen >
get_config()->header_field_buffer ||
downstream->get_response_headers().size() >=
get_config()->max_header_fields) {
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
DLOG(INFO, downstream) << "Too large header block size=" DLOG(INFO, downstream)
<< downstream->get_response_headers_sum(); << "Too large or many header field size="
<< downstream->get_response_headers_sum() + namelen + valuelen
<< ", num=" << downstream->get_response_headers().size() + 1;
} }
if (trailer) { if (trailer) {

View File

@ -202,14 +202,19 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
return 0; return 0;
} }
if (downstream->get_request_headers_sum() > Downstream::MAX_HEADERS_SUM) { if (downstream->get_request_headers_sum() + namelen + valuelen >
get_config()->header_field_buffer ||
downstream->get_request_headers().size() >=
get_config()->max_header_fields) {
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) { if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
return 0; return 0;
} }
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
ULOG(INFO, upstream) << "Too large header block size=" ULOG(INFO, upstream) << "Too large or many header field size="
<< downstream->get_request_headers_sum(); << downstream->get_request_headers_sum() + namelen +
valuelen << ", num="
<< downstream->get_request_headers().size() + 1;
} }
// just ignore header fields if this is trailer part. // just ignore header fields if this is trailer part.

View File

@ -582,10 +582,29 @@ int htp_hdrs_completecb(http_parser *htp) {
namespace { namespace {
int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) { int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
auto downstream = static_cast<Downstream *>(htp->data); auto downstream = static_cast<Downstream *>(htp->data);
if (downstream->get_response_headers_sum() + len >
get_config()->header_field_buffer) {
if (LOG_ENABLED(INFO)) {
DLOG(INFO, downstream) << "Too large header block size="
<< downstream->get_response_headers_sum() + len;
}
return -1;
}
if (downstream->get_response_state() == Downstream::INITIAL) { if (downstream->get_response_state() == Downstream::INITIAL) {
if (downstream->get_response_header_key_prev()) { if (downstream->get_response_header_key_prev()) {
downstream->append_last_response_header_key(data, len); downstream->append_last_response_header_key(data, len);
} else { } else {
if (downstream->get_response_headers().size() >=
get_config()->max_header_fields) {
if (LOG_ENABLED(INFO)) {
DLOG(INFO, downstream)
<< "Too many header field num="
<< downstream->get_response_headers().size() + 1;
}
return -1;
}
downstream->add_response_header(std::string(data, len), ""); downstream->add_response_header(std::string(data, len), "");
} }
} else { } else {
@ -593,16 +612,18 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
if (downstream->get_response_trailer_key_prev()) { if (downstream->get_response_trailer_key_prev()) {
downstream->append_last_response_trailer_key(data, len); downstream->append_last_response_trailer_key(data, len);
} else { } else {
if (downstream->get_response_headers().size() >=
get_config()->max_header_fields) {
if (LOG_ENABLED(INFO)) {
DLOG(INFO, downstream)
<< "Too many header field num="
<< downstream->get_response_headers().size() + 1;
}
return -1;
}
downstream->add_response_trailer(std::string(data, len), ""); downstream->add_response_trailer(std::string(data, len), "");
} }
} }
if (downstream->get_response_headers_sum() > Downstream::MAX_HEADERS_SUM) {
if (LOG_ENABLED(INFO)) {
DLOG(INFO, downstream) << "Too large header block size="
<< downstream->get_response_headers_sum();
}
return -1;
}
return 0; return 0;
} }
} // namespace } // namespace
@ -610,6 +631,14 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
namespace { namespace {
int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) { int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) {
auto downstream = static_cast<Downstream *>(htp->data); auto downstream = static_cast<Downstream *>(htp->data);
if (downstream->get_response_headers_sum() + len >
get_config()->header_field_buffer) {
if (LOG_ENABLED(INFO)) {
DLOG(INFO, downstream) << "Too large header block size="
<< downstream->get_response_headers_sum() + len;
}
return -1;
}
if (downstream->get_response_state() == Downstream::INITIAL) { if (downstream->get_response_state() == Downstream::INITIAL) {
if (downstream->get_response_header_key_prev()) { if (downstream->get_response_header_key_prev()) {
downstream->set_last_response_header_value(data, len); downstream->set_last_response_header_value(data, len);
@ -623,13 +652,6 @@ int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) {
downstream->append_last_response_trailer_value(data, len); downstream->append_last_response_trailer_value(data, len);
} }
} }
if (downstream->get_response_headers_sum() > Downstream::MAX_HEADERS_SUM) {
if (LOG_ENABLED(INFO)) {
DLOG(INFO, downstream) << "Too large header block size="
<< downstream->get_response_headers_sum();
}
return -1;
}
return 0; return 0;
} }
} // namespace } // namespace

View File

@ -78,6 +78,17 @@ namespace {
int htp_uricb(http_parser *htp, const char *data, size_t len) { int htp_uricb(http_parser *htp, const char *data, size_t len) {
auto upstream = static_cast<HttpsUpstream *>(htp->data); auto upstream = static_cast<HttpsUpstream *>(htp->data);
auto downstream = upstream->get_downstream(); auto downstream = upstream->get_downstream();
if (downstream->get_request_headers_sum() + len >
get_config()->header_field_buffer) {
if (LOG_ENABLED(INFO)) {
ULOG(INFO, upstream) << "Too large URI size="
<< downstream->get_request_headers_sum() + len;
}
assert(downstream->get_request_state() == Downstream::INITIAL);
downstream->set_request_state(Downstream::HTTP1_REQUEST_HEADER_TOO_LARGE);
return -1;
}
downstream->add_request_headers_sum(len);
downstream->append_request_path(data, len); downstream->append_request_path(data, len);
return 0; return 0;
} }
@ -87,10 +98,31 @@ namespace {
int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) { int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
auto upstream = static_cast<HttpsUpstream *>(htp->data); auto upstream = static_cast<HttpsUpstream *>(htp->data);
auto downstream = upstream->get_downstream(); auto downstream = upstream->get_downstream();
if (downstream->get_request_headers_sum() + len >
get_config()->header_field_buffer) {
if (LOG_ENABLED(INFO)) {
ULOG(INFO, upstream) << "Too large header block size="
<< downstream->get_request_headers_sum() + len;
}
if (downstream->get_request_state() == Downstream::INITIAL) {
downstream->set_request_state(Downstream::HTTP1_REQUEST_HEADER_TOO_LARGE);
}
return -1;
}
if (downstream->get_request_state() == Downstream::INITIAL) { if (downstream->get_request_state() == Downstream::INITIAL) {
if (downstream->get_request_header_key_prev()) { if (downstream->get_request_header_key_prev()) {
downstream->append_last_request_header_key(data, len); downstream->append_last_request_header_key(data, len);
} else { } else {
if (downstream->get_request_headers().size() >=
get_config()->max_header_fields) {
if (LOG_ENABLED(INFO)) {
ULOG(INFO, upstream) << "Too many header field num="
<< downstream->get_request_headers().size() + 1;
}
downstream->set_request_state(
Downstream::HTTP1_REQUEST_HEADER_TOO_LARGE);
return -1;
}
downstream->add_request_header(std::string(data, len), ""); downstream->add_request_header(std::string(data, len), "");
} }
} else { } else {
@ -98,16 +130,17 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
if (downstream->get_request_trailer_key_prev()) { if (downstream->get_request_trailer_key_prev()) {
downstream->append_last_request_trailer_key(data, len); downstream->append_last_request_trailer_key(data, len);
} else { } else {
if (downstream->get_request_headers().size() >=
get_config()->max_header_fields) {
if (LOG_ENABLED(INFO)) {
ULOG(INFO, upstream) << "Too many header field num="
<< downstream->get_request_headers().size() + 1;
}
return -1;
}
downstream->add_request_trailer(std::string(data, len), ""); downstream->add_request_trailer(std::string(data, len), "");
} }
} }
if (downstream->get_request_headers_sum() > Downstream::MAX_HEADERS_SUM) {
if (LOG_ENABLED(INFO)) {
ULOG(INFO, upstream) << "Too large header block size="
<< downstream->get_request_headers_sum();
}
return -1;
}
return 0; return 0;
} }
} // namespace } // namespace
@ -116,6 +149,17 @@ namespace {
int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) { int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) {
auto upstream = static_cast<HttpsUpstream *>(htp->data); auto upstream = static_cast<HttpsUpstream *>(htp->data);
auto downstream = upstream->get_downstream(); auto downstream = upstream->get_downstream();
if (downstream->get_request_headers_sum() + len >
get_config()->header_field_buffer) {
if (LOG_ENABLED(INFO)) {
ULOG(INFO, upstream) << "Too large header block size="
<< downstream->get_request_headers_sum() + len;
}
if (downstream->get_request_state() == Downstream::INITIAL) {
downstream->set_request_state(Downstream::HTTP1_REQUEST_HEADER_TOO_LARGE);
}
return -1;
}
if (downstream->get_request_state() == Downstream::INITIAL) { if (downstream->get_request_state() == Downstream::INITIAL) {
if (downstream->get_request_header_key_prev()) { if (downstream->get_request_header_key_prev()) {
downstream->set_last_request_header_value(data, len); downstream->set_last_request_header_value(data, len);
@ -129,13 +173,6 @@ int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) {
downstream->append_last_request_trailer_value(data, len); downstream->append_last_request_trailer_value(data, len);
} }
} }
if (downstream->get_request_headers_sum() > Downstream::MAX_HEADERS_SUM) {
if (LOG_ENABLED(INFO)) {
ULOG(INFO, upstream) << "Too large header block size="
<< downstream->get_request_headers_sum();
}
return -1;
}
return 0; return 0;
} }
} // namespace } // namespace
@ -181,7 +218,12 @@ void rewrite_request_host_path_from_uri(Downstream *downstream, const char *uri,
path += '?'; path += '?';
path.append(uri + fdata.off, fdata.len); path.append(uri + fdata.off, fdata.len);
} }
downstream->set_request_path(path); downstream->set_request_path(std::move(path));
if (get_config()->http2_proxy || get_config()->client_proxy) {
std::string scheme;
http2::copy_url_component(scheme, &u, UF_SCHEMA, uri);
downstream->set_request_http2_scheme(std::move(scheme));
}
} }
} // namespace } // namespace
@ -229,22 +271,23 @@ int htp_hdrs_completecb(http_parser *htp) {
if (downstream->get_request_method() != "CONNECT") { if (downstream->get_request_method() != "CONNECT") {
http_parser_url u{}; http_parser_url u{};
auto uri = downstream->get_request_path().c_str(); // make a copy of request path, since we may set request path
rv = http_parser_parse_url(uri, downstream->get_request_path().size(), 0, // while we are refering to original request path.
&u); auto uri = downstream->get_request_path();
rv = http_parser_parse_url(uri.c_str(),
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;
} }
// checking UF_HOST could be redundant, but just in case ... // checking UF_HOST could be redundant, but just in case ...
if (!(u.field_set & (1 << UF_SCHEMA)) || !(u.field_set & (1 << UF_HOST))) { if (!(u.field_set & (1 << UF_SCHEMA)) || !(u.field_set & (1 << UF_HOST))) {
if (get_config()->client_proxy) { if (get_config()->http2_proxy || get_config()->client_proxy) {
// Request URI should be absolute-form for client proxy mode // Request URI should be absolute-form for client proxy mode
return -1; return -1;
} }
} else { } else {
rewrite_request_host_path_from_uri(downstream, uri, u); rewrite_request_host_path_from_uri(downstream, uri.c_str(), u);
// uri could be invalidated here
} }
} }
@ -407,9 +450,15 @@ int HttpsUpstream::on_read() {
unsigned int status_code; unsigned int status_code;
if (downstream && if (downstream) {
downstream->get_request_state() == Downstream::CONNECT_FAIL) { if (downstream->get_request_state() == Downstream::CONNECT_FAIL) {
status_code = 503; status_code = 503;
} else if (downstream->get_request_state() ==
Downstream::HTTP1_REQUEST_HEADER_TOO_LARGE) {
status_code = 431;
} else {
status_code = 400;
}
} else { } else {
status_code = 400; status_code = 400;
} }

View File

@ -158,7 +158,8 @@ std::pair<OutputIterator, size_t> copy(const char *src, size_t avail,
} }
} // namespace } // namespace
void upstream_accesslog(const std::vector<LogFragment> &lfv, LogSpec *lgsp) { void upstream_accesslog(const std::vector<LogFragment> &lfv,
const LogSpec &lgsp) {
auto lgconf = log_config(); auto lgconf = log_config();
if (lgconf->accesslog_fd == -1 && !get_config()->accesslog_syslog) { if (lgconf->accesslog_fd == -1 && !get_config()->accesslog_syslog) {
@ -167,12 +168,12 @@ void upstream_accesslog(const std::vector<LogFragment> &lfv, LogSpec *lgsp) {
char buf[4096]; char buf[4096];
auto downstream = lgsp->downstream; auto downstream = lgsp.downstream;
auto p = buf; auto p = buf;
auto avail = sizeof(buf) - 2; auto avail = sizeof(buf) - 2;
lgconf->update_tstamp(lgsp->time_now); lgconf->update_tstamp(lgsp.time_now);
auto &time_local = lgconf->time_local_str; auto &time_local = lgconf->time_local_str;
auto &time_iso8601 = lgconf->time_iso8601_str; auto &time_iso8601 = lgconf->time_iso8601_str;
@ -182,7 +183,7 @@ void upstream_accesslog(const std::vector<LogFragment> &lfv, LogSpec *lgsp) {
std::tie(p, avail) = copy(lf.value.get(), avail, p); std::tie(p, avail) = copy(lf.value.get(), avail, p);
break; break;
case SHRPX_LOGF_REMOTE_ADDR: case SHRPX_LOGF_REMOTE_ADDR:
std::tie(p, avail) = copy(lgsp->remote_addr, avail, p); std::tie(p, avail) = copy(lgsp.remote_addr, avail, p);
break; break;
case SHRPX_LOGF_TIME_LOCAL: case SHRPX_LOGF_TIME_LOCAL:
std::tie(p, avail) = copy(time_local.c_str(), avail, p); std::tie(p, avail) = copy(time_local.c_str(), avail, p);
@ -191,22 +192,22 @@ void upstream_accesslog(const std::vector<LogFragment> &lfv, LogSpec *lgsp) {
std::tie(p, avail) = copy(time_iso8601.c_str(), avail, p); std::tie(p, avail) = copy(time_iso8601.c_str(), avail, p);
break; break;
case SHRPX_LOGF_REQUEST: case SHRPX_LOGF_REQUEST:
std::tie(p, avail) = copy(lgsp->method, avail, p); std::tie(p, avail) = copy(lgsp.method, avail, p);
std::tie(p, avail) = copy(" ", avail, p); std::tie(p, avail) = copy(" ", avail, p);
std::tie(p, avail) = copy(lgsp->path, avail, p); std::tie(p, avail) = copy(lgsp.path, avail, p);
std::tie(p, avail) = copy(" HTTP/", avail, p); std::tie(p, avail) = copy(" HTTP/", avail, p);
std::tie(p, avail) = copy(util::utos(lgsp->major).c_str(), avail, p); std::tie(p, avail) = copy(util::utos(lgsp.major).c_str(), avail, p);
if (lgsp->major < 2) { if (lgsp.major < 2) {
std::tie(p, avail) = copy(".", avail, p); std::tie(p, avail) = copy(".", avail, p);
std::tie(p, avail) = copy(util::utos(lgsp->minor).c_str(), avail, p); std::tie(p, avail) = copy(util::utos(lgsp.minor).c_str(), avail, p);
} }
break; break;
case SHRPX_LOGF_STATUS: case SHRPX_LOGF_STATUS:
std::tie(p, avail) = copy(util::utos(lgsp->status).c_str(), avail, p); std::tie(p, avail) = copy(util::utos(lgsp.status).c_str(), avail, p);
break; break;
case SHRPX_LOGF_BODY_BYTES_SENT: case SHRPX_LOGF_BODY_BYTES_SENT:
std::tie(p, avail) = std::tie(p, avail) =
copy(util::utos(lgsp->body_bytes_sent).c_str(), avail, p); copy(util::utos(lgsp.body_bytes_sent).c_str(), avail, p);
break; break;
case SHRPX_LOGF_HTTP: case SHRPX_LOGF_HTTP:
if (downstream) { if (downstream) {
@ -221,15 +222,14 @@ void upstream_accesslog(const std::vector<LogFragment> &lfv, LogSpec *lgsp) {
break; break;
case SHRPX_LOGF_REMOTE_PORT: case SHRPX_LOGF_REMOTE_PORT:
std::tie(p, avail) = copy(lgsp->remote_port, avail, p); std::tie(p, avail) = copy(lgsp.remote_port, avail, p);
break; break;
case SHRPX_LOGF_SERVER_PORT: case SHRPX_LOGF_SERVER_PORT:
std::tie(p, avail) = std::tie(p, avail) = copy(util::utos(lgsp.server_port).c_str(), avail, p);
copy(util::utos(lgsp->server_port).c_str(), avail, p);
break; break;
case SHRPX_LOGF_REQUEST_TIME: { case SHRPX_LOGF_REQUEST_TIME: {
auto t = std::chrono::duration_cast<std::chrono::milliseconds>( auto t = std::chrono::duration_cast<std::chrono::milliseconds>(
lgsp->request_end_time - lgsp->request_start_time).count(); lgsp.request_end_time - lgsp.request_start_time).count();
auto frac = util::utos(t % 1000); auto frac = util::utos(t % 1000);
auto sec = util::utos(t / 1000); auto sec = util::utos(t / 1000);
@ -242,10 +242,10 @@ void upstream_accesslog(const std::vector<LogFragment> &lfv, LogSpec *lgsp) {
std::tie(p, avail) = copy(sec.c_str(), avail, p); std::tie(p, avail) = copy(sec.c_str(), avail, p);
} break; } break;
case SHRPX_LOGF_PID: case SHRPX_LOGF_PID:
std::tie(p, avail) = copy(util::utos(lgsp->pid).c_str(), avail, p); std::tie(p, avail) = copy(util::utos(lgsp.pid).c_str(), avail, p);
break; break;
case SHRPX_LOGF_ALPN: case SHRPX_LOGF_ALPN:
std::tie(p, avail) = copy(lgsp->alpn, avail, p); std::tie(p, avail) = copy(lgsp.alpn, avail, p);
break; break;
case SHRPX_LOGF_NONE: case SHRPX_LOGF_NONE:
break; break;

View File

@ -139,7 +139,8 @@ struct LogSpec {
pid_t pid; pid_t pid;
}; };
void upstream_accesslog(const std::vector<LogFragment> &lf, LogSpec *lgsp); void upstream_accesslog(const std::vector<LogFragment> &lf,
const LogSpec &lgsp);
int reopen_log_files(); int reopen_log_files();

View File

@ -163,6 +163,19 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
<< downstream->get_stream_id() << "\n" << ss.str(); << downstream->get_stream_id() << "\n" << ss.str();
} }
size_t num_headers = 0;
size_t header_buffer = 0;
for (size_t i = 0; nv[i]; i += 2) {
++num_headers;
header_buffer += strlen(nv[i]) + strlen(nv[i + 1]);
}
if (header_buffer > get_config()->header_field_buffer ||
num_headers > get_config()->max_header_fields) {
upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
return;
}
for (size_t i = 0; nv[i]; i += 2) { for (size_t i = 0; nv[i]; i += 2) {
downstream->add_request_header(nv[i], nv[i + 1]); downstream->add_request_header(nv[i], nv[i + 1]);
} }
@ -428,6 +441,12 @@ SpdyUpstream::SpdyUpstream(uint16_t version, ClientHandler *handler)
rv = spdylay_session_server_new(&session_, version, &callbacks, this); rv = spdylay_session_server_new(&session_, version, &callbacks, this);
assert(rv == 0); assert(rv == 0);
uint32_t max_buffer = 65536;
rv = spdylay_session_set_option(session_,
SPDYLAY_OPT_MAX_RECV_CTRL_FRAME_BUFFER,
&max_buffer, sizeof(max_buffer));
assert(rv == 0);
if (version >= SPDYLAY_PROTO_SPDY3) { if (version >= SPDYLAY_PROTO_SPDY3) {
int val = 1; int val = 1;
flow_control_ = true; flow_control_ = true;