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 are done by simply running `make check`.
Unit tests are done by simply running ``make check``.
Integration tests
-----------------

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
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_INIT()
dnl See versioning rule:
dnl http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
AC_SUBST(LT_CURRENT, 13)
AC_SUBST(LT_REVISION, 2)
AC_SUBST(LT_REVISION, 3)
AC_SUBST(LT_AGE, 8)
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.service
nghttpx-upstart.conf

View File

@ -21,19 +21,24 @@
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# 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'
nghttpx-init: Makefile
nghttpx-init: %: $(srcdir)/%.in
rm -f $@ $@.tmp
$(edit) $(srcdir)/$@.in > $@.tmp
$(edit) $< > $@.tmp
chmod +x $@.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:
-rm -f nghttpx-init nghttpx-init.tmp
-rm -f nghttpx-init.tmp $(configfiles)

View File

@ -1,18 +1,11 @@
/var/log/nghttpx/*.log {
weekly
missingok
rotate 52
missingok
compress
delaycompress
notifempty
create 0640 www-data adm
sharedscripts
prerotate
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`
killall -USR1 nghttpx 2> /dev/null || true
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
VERSION = (0, 1, 7)
VERSION = (0, 1, 8)
__version__ = ".".join(str(v) for v in VERSION)
__version_full__ = __version__

View File

@ -6,6 +6,7 @@
{% endfor %}
<li>{{ title }}</li>
<li class="wy-breadcrumbs-aside">
{% if pagename != "search" %}
{% if display_github %}
<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>
{% elif display_bitbucket %}
@ -15,6 +16,7 @@
{% elif show_source and has_source and sourcename %}
<a href="{{ pathto('_sources/' + sourcename, true)|e }}" rel="nofollow"> View page source</a>
{% endif %}
{% endif %}
</li>
</ul>
<hr/>

View File

@ -2,10 +2,10 @@
{% if next or prev %}
<div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
{% 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 %}
{% 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 %}
</div>
{% endif %}

View File

@ -107,7 +107,7 @@
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
{% block menu %}
{% set toctree = toctree(maxdepth=2, collapse=False, includehidden=True) %}
{% set toctree = toctree(maxdepth=4, collapse=False, includehidden=True) %}
{% if toctree %}
{{ toctree }}
{% 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,47 +1,110 @@
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.
$(document).on('click', "[data-toggle='wy-nav-top']", function() {
$("[data-toggle='wy-nav-shift']").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() {
var target = $(this);
// 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() {
$("[data-toggle='rst-versions']").toggleClass("shift-up");
});
// Make tables responsive
$("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) {
var stickyNav = (function () {
var navBar,
win,
stickyNavCssClass = 'stickynav',
winScroll = false,
linkScroll = false,
winPosition = 0,
enable = function () {
navBar.addClass(stickyNavCssClass);
win.on('scroll', function() { // set flag on scroll event
init();
reset();
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 () {
if (winScroll) {
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 () {
navBar = jquery('nav.wy-nav-side:first');
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);
return {
enable : enable
enable: enable,
hashChange: hashChange
};
}());
return {

View File

@ -8,7 +8,7 @@ _nghttpd()
_get_comp_words_by_ref cur prev
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

View File

@ -8,7 +8,7 @@ _nghttpx()
_get_comp_words_by_ref cur prev
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

View File

@ -1,6 +1,6 @@
.\" 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
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.
.TP
.B sd
The standard deviation of the time for request and response.
The standard deviation of the time taken for request and response.
.TP
.B +/\- sd
The fraction of the number of requests within standard deviation
range (mean +/\- sd) against total number of successful requests.
.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
.SH FLOW CONTROL
.sp

View File

@ -153,11 +153,40 @@ time for request
mean
The mean time taken for request and response.
sd
The standard deviation of the time for request and response.
The standard deviation of the time taken for request and response.
+/- sd
The fraction of the number of requests within standard deviation
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
------------

View File

@ -44,11 +44,40 @@ time for request
mean
The mean time taken for request and response.
sd
The standard deviation of the time for request and response.
The standard deviation of the time taken for request and response.
+/- sd
The fraction of the number of requests within standard deviation
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
------------

View File

@ -1,6 +1,6 @@
.\" 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
nghttp \- HTTP/2 experimental client
.
@ -64,8 +64,9 @@ yet.
.UNINDENT
.INDENT 0.0
.TP
.B \-t, \-\-timeout=<SEC>
Timeout each request after <SEC> seconds.
.B \-t, \-\-timeout=<DURATION>
Timeout each request after <DURATION>. Set 0 to disable
timeout.
.UNINDENT
.INDENT 0.0
.TP
@ -215,6 +216,11 @@ Display this help and exit.
.sp
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).
.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
.sp
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
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>
@ -168,6 +169,11 @@ OPTIONS
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).
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
-------------------------

View File

@ -1,6 +1,6 @@
.\" 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
nghttpd \- HTTP/2 experimental server
.
@ -119,6 +119,14 @@ Specify 0 to disable padding.
.UNINDENT
.INDENT 0.0
.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>
Set the number of worker threads.
.sp

View File

@ -85,6 +85,13 @@ OPTIONS
Add at most <N> bytes to a frame payload as 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>
Set the number of worker threads.

View File

@ -1,6 +1,6 @@
.\" 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
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.
Example: \fI\%\-\-add\-response\-header\fP="foo: bar"
.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
.INDENT 0.0
.TP

View File

@ -619,6 +619,21 @@ HTTP
used several times to specify multiple header fields.
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
~~~~~

View File

@ -189,7 +189,7 @@ typedef enum {
NGHTTP2_TOKEN__METHOD,
NGHTTP2_TOKEN__PATH,
NGHTTP2_TOKEN__SCHEME,
NGHTTP2_TOKEN_HOST,
NGHTTP2_TOKEN_HOST
} nghttp2_token;
/* 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
// connection attempt to HTTP/2 backend failed.
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
func TestH2H1Upgrade(t *testing.T) {
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
// connection attempt to HTTP/2 backend failed.
func TestS3H2ConnectFailure(t *testing.T) {

View File

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

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) {
return a->namelen == b->namelen &&
a->name[a->namelen - 1] == b->name[a->namelen - 1] &&
memeq(a->name, b->name, a->namelen);
return a->namelen == b->namelen && memeq(a->name, b->name, a->namelen);
}
static int value_eq(const nghttp2_nv *a, const nghttp2_nv *b) {
return a->valuelen == b->valuelen &&
a->value[a->valuelen - 1] == b->value[a->valuelen - 1] &&
memeq(a->value, b->value, a->valuelen);
return a->valuelen == b->valuelen && memeq(a->value, b->value, a->valuelen);
}
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,
nghttp2_nv *nv,
nghttp2_hd_entry *ent_name) {
#ifndef NDEBUG
size_t rv;
#endif
size_t buflen;
uint8_t *buf;
nghttp2_mem *mem;
@ -1751,7 +1749,10 @@ static int hd_inflate_remove_bufs_with_name(nghttp2_hd_inflater *inflater,
/* Copy including terminal NULL */
memcpy(buf, ent_name->nv.name, ent_name->nv.namelen + 1);
rv = nghttp2_bufs_remove_copy(&inflater->nvbufs,
#ifndef NDEBUG
rv =
#endif
nghttp2_bufs_remove_copy(&inflater->nvbufs,
buf + ent_name->nv.namelen + 1);
assert(ent_name->nv.namelen + 1 + rv == buflen);

View File

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

View File

@ -58,7 +58,7 @@ typedef enum {
*/
NGHTTP2_OPT_PEER_MAX_CONCURRENT_STREAMS = 1 << 1,
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;
/**

View File

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

View File

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

View File

@ -133,7 +133,7 @@ typedef enum {
/* "http" or "https" scheme */
NGHTTP2_HTTP_FLAG_SCHEME_HTTP = 1 << 12,
/* 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;
typedef enum {

View File

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

View File

@ -62,6 +62,7 @@ struct Config {
void *data_ptr;
size_t padding;
size_t num_worker;
size_t max_concurrent_streams;
ssize_t header_table_size;
uint16_t port;
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)
: worker(worker), ssl(nullptr), next_addr(config.addrs), reqidx(0),
state(CLIENT_IDLE), req_todo(req_todo), req_started(0), req_done(0),
fd(-1) {
state(CLIENT_IDLE), first_byte_received(false), req_todo(req_todo),
req_started(0), req_done(0), fd(-1) {
ev_io_init(&wev, writecb, 0, EV_WRITE);
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::connect() {
record_start_time(&worker->stats);
while (next_addr) {
auto addr = next_addr;
next_addr = next_addr->ai_next;
@ -469,6 +471,8 @@ int Client::connection_made() {
session->on_connect();
record_connect_time(&worker->stats);
auto nreq =
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) {
return -1;
}
if (!first_byte_received) {
first_byte_received = true;
record_ttfb(&worker->stats);
}
}
return 0;
@ -641,6 +650,11 @@ int Client::read_tls() {
if (on_read(buf, rv) != 0) {
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();
}
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); }
Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients,
@ -731,70 +757,99 @@ void Worker::run() {
}
namespace {
double within_sd(const std::vector<std::unique_ptr<Worker>> &workers,
const std::chrono::microseconds &mean,
const std::chrono::microseconds &sd, size_t n) {
auto upper = mean.count() + sd.count();
auto lower = mean.count() - sd.count();
size_t m = 0;
for (const auto &w : workers) {
for (const auto &req_stat : w->stats.req_stats) {
if (!req_stat.completed) {
continue;
// Returns percentage of number of samples within mean +/- sd.
template <typename Duration>
double within_sd(const std::vector<Duration> &samples, const Duration &mean,
const Duration &sd) {
if (samples.size() == 0) {
return 0.0;
}
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;
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;
}
return (m / static_cast<double>(n)) * 100;
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 {
TimeStats
process_time_stats(const std::vector<std::unique_ptr<Worker>> &workers) {
auto ts = TimeStats();
int64_t sum = 0;
size_t n = 0;
size_t nrequest_times = 0, nttfb_times = 0;
for (const auto &w : workers) {
nrequest_times += w->stats.req_stats.size();
nttfb_times += w->stats.ttfbs.size();
}
ts.time_min = std::chrono::microseconds::max();
ts.time_max = std::chrono::microseconds::min();
ts.within_sd = 0.;
std::vector<std::chrono::microseconds> request_times;
request_times.reserve(nrequest_times);
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 &req_stat : w->stats.req_stats) {
if (!req_stat.completed) {
continue;
}
++n;
auto t = std::chrono::duration_cast<std::chrono::microseconds>(
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;
q = q + (t.count() - a) * (t.count() - na);
a = na;
}
}
if (n == 0) {
ts.time_max = ts.time_min = std::chrono::microseconds::zero();
return ts;
request_times.push_back(
std::chrono::duration_cast<std::chrono::microseconds>(
req_stat.stream_close_time - req_stat.request_time));
}
ts.time_mean = std::chrono::microseconds(sum / n);
ts.time_sd = std::chrono::microseconds(
static_cast<std::chrono::microseconds::rep>(sqrt(q / n)));
const auto &stat = w->stats;
// rule out cases where we started but didn't connect or get the
// 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]));
ts.within_sd = within_sd(workers, ts.time_mean, ts.time_sd, n);
return ts;
ttfb_times.push_back(
std::chrono::duration_cast<std::chrono::microseconds>(
stat.ttfbs[i] - stat.start_times[i]));
}
}
return {compute_time_stat(request_times), compute_time_stat(connect_times),
compute_time_stat(ttfb_times)};
}
} // 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
// 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
<< " bytes headers, " << stats.bytes_body << R"( bytes data
min max mean sd +/- sd
time for request: )" << std::setw(10)
<< util::format_duration(time_stats.time_min) << " "
<< std::setw(10) << util::format_duration(time_stats.time_max)
<< " " << std::setw(10)
<< util::format_duration(time_stats.time_mean) << " "
<< std::setw(10) << util::format_duration(time_stats.time_sd)
<< std::setw(9) << util::dtos(time_stats.within_sd) << "%"
<< std::endl;
time for request: )" << std::setw(10) << util::format_duration(ts.request.min)
<< " " << std::setw(10) << util::format_duration(ts.request.max)
<< " " << std::setw(10) << util::format_duration(ts.request.mean)
<< " " << std::setw(10) << util::format_duration(ts.request.sd)
<< std::setw(9) << util::dtos(ts.request.within_sd) << "%"
<< "\ntime for connect: " << std::setw(10)
<< util::format_duration(ts.connect.min) << " " << std::setw(10)
<< 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);

View File

@ -94,13 +94,25 @@ struct RequestStat {
bool completed;
};
struct TimeStats {
// time for request: max, min, mean and sd (standard deviation)
std::chrono::microseconds time_max, time_min, time_mean, time_sd;
// percentage of number of requests inside mean -/+ sd
template<typename Duration>
struct TimeStat {
// min, max, mean and sd (standard deviation)
Duration min, max, mean, sd;
// percentage of samples inside mean -/+ 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 {
Stats(size_t req_todo);
// The total number of requests
@ -132,6 +144,12 @@ struct Stats {
std::array<size_t, 6> status;
// The statistics per request
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 };
@ -171,6 +189,7 @@ struct Client {
addrinfo *next_addr;
size_t reqidx;
ClientState state;
bool first_byte_received;
// The number of requests this client has to issue.
size_t req_todo;
// 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 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();
};

View File

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

View File

@ -88,6 +88,7 @@ void print_usage(std::ostream &out) {
namespace {
void print_help(std::ostream &out) {
Config config;
print_usage(out);
out << R"(
<PORT> Specify listening port number.
@ -128,6 +129,10 @@ Options:
-b, --padding=<N>
Add at most <N> bytes to a frame payload as 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>
Set the number of worker threads.
Default: 1
@ -173,6 +178,7 @@ int main(int argc, char **argv) {
{"header-table-size", required_argument, nullptr, 'c'},
{"push", required_argument, nullptr, 'p'},
{"padding", required_argument, nullptr, 'b'},
{"max-concurrent-streams", required_argument, nullptr, 'm'},
{"workers", required_argument, nullptr, 'n'},
{"error-gzip", no_argument, nullptr, 'e'},
{"no-tls", no_argument, &flag, 1},
@ -184,7 +190,7 @@ int main(int argc, char **argv) {
{"hexdump", no_argument, &flag, 7},
{nullptr, 0, nullptr, 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);
char *end;
if (c == -1) {
@ -209,6 +215,16 @@ int main(int argc, char **argv) {
case 'e':
config.error_gzip = true;
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':
#ifdef NOTHREADS
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 =
strcopy(PKGDATADIR "/fetch-ocsp-response");
mod_config()->no_ocsp = false;
mod_config()->header_field_buffer = 64 * 1024;
mod_config()->max_header_fields = 100;
}
} // namespace
@ -1337,6 +1339,16 @@ HTTP:
won't replace anything already set. This option can be
used several times to specify multiple header fields.
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:
--frontend-http2-dump-request-header=<PATH>
@ -1497,6 +1509,8 @@ int main(int argc, char **argv) {
{"fetch-ocsp-response-file", required_argument, &flag, 77},
{"ocsp-update-interval", required_argument, &flag, 78},
{"no-ocsp", no_argument, &flag, 79},
{"header-field-buffer", required_argument, &flag, 80},
{"max-header-fields", required_argument, &flag, 81},
{nullptr, 0, nullptr, 0}};
int option_index = 0;
@ -1847,6 +1861,14 @@ int main(int argc, char **argv) {
// --no-ocsp
cmdcfgs.emplace_back(SHRPX_OPT_NO_OCSP, "yes");
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:
break;
}

View File

@ -684,11 +684,61 @@ void ClientHandler::start_immediate_shutdown() {
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) {
LogSpec lgsp = {
upstream_accesslog(
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" &&
(get_config()->http2_proxy || get_config()->client_proxy))
? construct_absolute_request_uri(downstream).c_str()
: downstream->get_request_path().empty()
? downstream->get_request_http2_authority().c_str()
: downstream->get_request_path().c_str(),
@ -702,9 +752,7 @@ void ClientHandler::write_accesslog(Downstream *downstream) {
downstream->get_response_http_status(),
downstream->get_response_sent_bodylen(), port_.c_str(),
get_config()->port, get_config()->pid,
};
upstream_accesslog(get_config()->accesslog_format, &lgsp);
});
}
void ClientHandler::write_accesslog(int major, int minor, unsigned int status,
@ -712,7 +760,8 @@ void ClientHandler::write_accesslog(int major, int minor, unsigned int status,
auto time_now = std::chrono::system_clock::now();
auto highres_now = std::chrono::high_resolution_clock::now();
LogSpec lgsp = {
upstream_accesslog(get_config()->accesslog_format,
LogSpec{
nullptr, ipaddr_.c_str(),
"-", // method
"-", // path,
@ -723,9 +772,7 @@ void ClientHandler::write_accesslog(int major, int minor, unsigned int status,
major, minor, // major, minor
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_; }

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_OCSP_UPDATE_INTERVAL[] = "ocsp-update-interval";
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 {
Config *config = nullptr;
@ -1212,6 +1214,15 @@ int parse_config(const char *opt, const char *optarg) {
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")) {
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_OCSP_UPDATE_INTERVAL[];
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 {
sockaddr_storage storage;
@ -282,6 +284,8 @@ struct Config {
size_t rlimit_nofile;
size_t downstream_request_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
// passed to SSL_CTX_set_options().
long int tls_proto_mask;

View File

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

View File

@ -143,6 +143,7 @@ public:
void set_request_method(std::string method);
const std::string &get_request_method() const;
void set_request_path(std::string path);
void add_request_headers_sum(size_t amount);
void
set_request_start_time(std::chrono::high_resolution_clock::time_point time);
const std::chrono::high_resolution_clock::time_point &
@ -193,6 +194,10 @@ public:
// header contains invalid header field. We can safely send error
// response (502) to a client.
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);
int get_request_state() const;
@ -286,9 +291,6 @@ public:
// Change the priority of downstream
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;
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 &&
!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)) {
DLOG(INFO, downstream) << "Too large header block size="
<< downstream->get_response_headers_sum();
DLOG(INFO, downstream)
<< "Too large or many header field size="
<< downstream->get_response_headers_sum() + namelen + valuelen
<< ", num=" << downstream->get_response_headers().size() + 1;
}
if (trailer) {

View File

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

View File

@ -582,10 +582,29 @@ int htp_hdrs_completecb(http_parser *htp) {
namespace {
int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
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_header_key_prev()) {
downstream->append_last_response_header_key(data, len);
} 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), "");
}
} 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()) {
downstream->append_last_response_trailer_key(data, len);
} else {
downstream->add_response_trailer(std::string(data, len), "");
}
}
if (downstream->get_response_headers_sum() > Downstream::MAX_HEADERS_SUM) {
if (downstream->get_response_headers().size() >=
get_config()->max_header_fields) {
if (LOG_ENABLED(INFO)) {
DLOG(INFO, downstream) << "Too large header block size="
<< downstream->get_response_headers_sum();
DLOG(INFO, downstream)
<< "Too many header field num="
<< downstream->get_response_headers().size() + 1;
}
return -1;
}
downstream->add_response_trailer(std::string(data, len), "");
}
}
return 0;
}
} // namespace
@ -610,6 +631,14 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
namespace {
int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) {
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_header_key_prev()) {
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);
}
}
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;
}
} // namespace

View File

@ -78,6 +78,17 @@ namespace {
int htp_uricb(http_parser *htp, const char *data, size_t len) {
auto upstream = static_cast<HttpsUpstream *>(htp->data);
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);
return 0;
}
@ -87,10 +98,31 @@ namespace {
int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
auto upstream = static_cast<HttpsUpstream *>(htp->data);
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_header_key_prev()) {
downstream->append_last_request_header_key(data, len);
} 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), "");
}
} 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()) {
downstream->append_last_request_trailer_key(data, len);
} else {
downstream->add_request_trailer(std::string(data, len), "");
}
}
if (downstream->get_request_headers_sum() > Downstream::MAX_HEADERS_SUM) {
if (downstream->get_request_headers().size() >=
get_config()->max_header_fields) {
if (LOG_ENABLED(INFO)) {
ULOG(INFO, upstream) << "Too large header block size="
<< downstream->get_request_headers_sum();
ULOG(INFO, upstream) << "Too many header field num="
<< downstream->get_request_headers().size() + 1;
}
return -1;
}
downstream->add_request_trailer(std::string(data, len), "");
}
}
return 0;
}
} // namespace
@ -116,6 +149,17 @@ namespace {
int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) {
auto upstream = static_cast<HttpsUpstream *>(htp->data);
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_header_key_prev()) {
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);
}
}
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;
}
} // namespace
@ -181,7 +218,12 @@ void rewrite_request_host_path_from_uri(Downstream *downstream, const char *uri,
path += '?';
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
@ -229,22 +271,23 @@ int htp_hdrs_completecb(http_parser *htp) {
if (downstream->get_request_method() != "CONNECT") {
http_parser_url u{};
auto uri = downstream->get_request_path().c_str();
rv = http_parser_parse_url(uri, downstream->get_request_path().size(), 0,
&u);
// make a copy of request path, since we may set request path
// while we are refering to original request path.
auto uri = downstream->get_request_path();
rv = http_parser_parse_url(uri.c_str(),
downstream->get_request_path().size(), 0, &u);
if (rv != 0) {
// Expect to respond with 400 bad request
return -1;
}
// checking UF_HOST could be redundant, but just in case ...
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
return -1;
}
} else {
rewrite_request_host_path_from_uri(downstream, uri, u);
// uri could be invalidated here
rewrite_request_host_path_from_uri(downstream, uri.c_str(), u);
}
}
@ -407,9 +450,15 @@ int HttpsUpstream::on_read() {
unsigned int status_code;
if (downstream &&
downstream->get_request_state() == Downstream::CONNECT_FAIL) {
if (downstream) {
if (downstream->get_request_state() == Downstream::CONNECT_FAIL) {
status_code = 503;
} else if (downstream->get_request_state() ==
Downstream::HTTP1_REQUEST_HEADER_TOO_LARGE) {
status_code = 431;
} else {
status_code = 400;
}
} else {
status_code = 400;
}

View File

@ -158,7 +158,8 @@ std::pair<OutputIterator, size_t> copy(const char *src, size_t avail,
}
} // 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();
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];
auto downstream = lgsp->downstream;
auto downstream = lgsp.downstream;
auto p = buf;
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_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);
break;
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;
case SHRPX_LOGF_TIME_LOCAL:
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);
break;
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(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(util::utos(lgsp->major).c_str(), avail, p);
if (lgsp->major < 2) {
std::tie(p, avail) = copy(util::utos(lgsp.major).c_str(), avail, p);
if (lgsp.major < 2) {
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;
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;
case SHRPX_LOGF_BODY_BYTES_SENT:
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;
case SHRPX_LOGF_HTTP:
if (downstream) {
@ -221,15 +222,14 @@ void upstream_accesslog(const std::vector<LogFragment> &lfv, LogSpec *lgsp) {
break;
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;
case SHRPX_LOGF_SERVER_PORT:
std::tie(p, avail) =
copy(util::utos(lgsp->server_port).c_str(), avail, p);
std::tie(p, avail) = copy(util::utos(lgsp.server_port).c_str(), avail, p);
break;
case SHRPX_LOGF_REQUEST_TIME: {
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 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);
} break;
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;
case SHRPX_LOGF_ALPN:
std::tie(p, avail) = copy(lgsp->alpn, avail, p);
std::tie(p, avail) = copy(lgsp.alpn, avail, p);
break;
case SHRPX_LOGF_NONE:
break;

View File

@ -139,7 +139,8 @@ struct LogSpec {
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();

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();
}
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) {
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);
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) {
int val = 1;
flow_control_ = true;