@@ -174,6 +174,18 @@ Http2Options::Http2Options(Environment* env) {
174
174
if (flags & (1 << IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS)) {
175
175
SetMaxOutstandingSettings (buffer[IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS]);
176
176
}
177
+
178
+ // The HTTP2 specification places no limits on the amount of memory
179
+ // that a session can consume. In order to prevent abuse, we place a
180
+ // cap on the amount of memory a session can consume at any given time.
181
+ // this is a credit based system. Existing streams may cause the limit
182
+ // to be temporarily exceeded but once over the limit, new streams cannot
183
+ // created.
184
+ // Important: The maxSessionMemory option in javascript is expressed in
185
+ // terms of MB increments (i.e. the value 1 == 1 MB)
186
+ if (flags & (1 << IDX_OPTIONS_MAX_SESSION_MEMORY)) {
187
+ SetMaxSessionMemory (buffer[IDX_OPTIONS_MAX_SESSION_MEMORY] * 1e6 );
188
+ }
177
189
}
178
190
179
191
void Http2Session::Http2Settings::Init () {
@@ -482,11 +494,13 @@ Http2Session::Http2Session(Environment* env,
482
494
// Capture the configuration options for this session
483
495
Http2Options opts (env);
484
496
485
- int32_t maxHeaderPairs = opts.GetMaxHeaderPairs ();
497
+ max_session_memory_ = opts.GetMaxSessionMemory ();
498
+
499
+ uint32_t maxHeaderPairs = opts.GetMaxHeaderPairs ();
486
500
max_header_pairs_ =
487
501
type == NGHTTP2_SESSION_SERVER
488
- ? std::max (maxHeaderPairs, 4 ) // minimum # of request headers
489
- : std::max (maxHeaderPairs, 1 ); // minimum # of response headers
502
+ ? std::max (maxHeaderPairs, 4U ) // minimum # of request headers
503
+ : std::max (maxHeaderPairs, 1U ); // minimum # of response headers
490
504
491
505
max_outstanding_pings_ = opts.GetMaxOutstandingPings ();
492
506
max_outstanding_settings_ = opts.GetMaxOutstandingSettings ();
@@ -672,18 +686,21 @@ inline bool Http2Session::CanAddStream() {
672
686
size_t maxSize =
673
687
std::min (streams_.max_size (), static_cast <size_t >(maxConcurrentStreams));
674
688
// We can add a new stream so long as we are less than the current
675
- // maximum on concurrent streams
676
- return streams_.size () < maxSize;
689
+ // maximum on concurrent streams and there's enough available memory
690
+ return streams_.size () < maxSize &&
691
+ IsAvailableSessionMemory (sizeof (Http2Stream));
677
692
}
678
693
679
694
inline void Http2Session::AddStream (Http2Stream* stream) {
680
695
CHECK_GE (++statistics_.stream_count , 0 );
681
696
streams_[stream->id ()] = stream;
697
+ IncrementCurrentSessionMemory (stream->self_size ());
682
698
}
683
699
684
700
685
- inline void Http2Session::RemoveStream (int32_t id) {
686
- streams_.erase (id);
701
+ inline void Http2Session::RemoveStream (Http2Stream* stream) {
702
+ streams_.erase (stream->id ());
703
+ DecrementCurrentSessionMemory (stream->self_size ());
687
704
}
688
705
689
706
// Used as one of the Padding Strategy functions. Will attempt to ensure
@@ -1677,7 +1694,7 @@ Http2Stream::Http2Stream(
1677
1694
1678
1695
Http2Stream::~Http2Stream () {
1679
1696
if (session_ != nullptr ) {
1680
- session_->RemoveStream (id_ );
1697
+ session_->RemoveStream (this );
1681
1698
session_ = nullptr ;
1682
1699
}
1683
1700
@@ -2007,7 +2024,7 @@ inline int Http2Stream::DoWrite(WriteWrap* req_wrap,
2007
2024
i == nbufs - 1 ? req_wrap : nullptr ,
2008
2025
bufs[i]
2009
2026
});
2010
- available_outbound_length_ += bufs[i].len ;
2027
+ IncrementAvailableOutboundLength ( bufs[i].len ) ;
2011
2028
}
2012
2029
CHECK_NE (nghttp2_session_resume_data (**session_, id_), NGHTTP2_ERR_NOMEM);
2013
2030
return 0 ;
@@ -2029,7 +2046,10 @@ inline bool Http2Stream::AddHeader(nghttp2_rcbuf* name,
2029
2046
if (this ->statistics_ .first_header == 0 )
2030
2047
this ->statistics_ .first_header = uv_hrtime ();
2031
2048
size_t length = GetBufferLength (name) + GetBufferLength (value) + 32 ;
2032
- if (current_headers_.size () == max_header_pairs_ ||
2049
+ // A header can only be added if we have not exceeded the maximum number
2050
+ // of headers and the session has memory available for it.
2051
+ if (!session_->IsAvailableSessionMemory (length) ||
2052
+ current_headers_.size () == max_header_pairs_ ||
2033
2053
current_headers_length_ + length > max_header_length_) {
2034
2054
return false ;
2035
2055
}
@@ -2173,7 +2193,7 @@ ssize_t Http2Stream::Provider::Stream::OnRead(nghttp2_session* handle,
2173
2193
// Just return the length, let Http2Session::OnSendData take care of
2174
2194
// actually taking the buffers out of the queue.
2175
2195
*flags |= NGHTTP2_DATA_FLAG_NO_COPY;
2176
- stream->available_outbound_length_ -= amount;
2196
+ stream->DecrementAvailableOutboundLength ( amount) ;
2177
2197
}
2178
2198
}
2179
2199
@@ -2196,6 +2216,15 @@ ssize_t Http2Stream::Provider::Stream::OnRead(nghttp2_session* handle,
2196
2216
return amount;
2197
2217
}
2198
2218
2219
+ inline void Http2Stream::IncrementAvailableOutboundLength (size_t amount) {
2220
+ available_outbound_length_ += amount;
2221
+ session_->IncrementCurrentSessionMemory (amount);
2222
+ }
2223
+
2224
+ inline void Http2Stream::DecrementAvailableOutboundLength (size_t amount) {
2225
+ available_outbound_length_ -= amount;
2226
+ session_->DecrementCurrentSessionMemory (amount);
2227
+ }
2199
2228
2200
2229
2201
2230
// Implementation of the JavaScript API
@@ -2689,6 +2718,7 @@ Http2Session::Http2Ping* Http2Session::PopPing() {
2689
2718
if (!outstanding_pings_.empty ()) {
2690
2719
ping = outstanding_pings_.front ();
2691
2720
outstanding_pings_.pop ();
2721
+ DecrementCurrentSessionMemory (ping->self_size ());
2692
2722
}
2693
2723
return ping;
2694
2724
}
@@ -2697,6 +2727,7 @@ bool Http2Session::AddPing(Http2Session::Http2Ping* ping) {
2697
2727
if (outstanding_pings_.size () == max_outstanding_pings_)
2698
2728
return false ;
2699
2729
outstanding_pings_.push (ping);
2730
+ IncrementCurrentSessionMemory (ping->self_size ());
2700
2731
return true ;
2701
2732
}
2702
2733
@@ -2705,6 +2736,7 @@ Http2Session::Http2Settings* Http2Session::PopSettings() {
2705
2736
if (!outstanding_settings_.empty ()) {
2706
2737
settings = outstanding_settings_.front ();
2707
2738
outstanding_settings_.pop ();
2739
+ DecrementCurrentSessionMemory (settings->self_size ());
2708
2740
}
2709
2741
return settings;
2710
2742
}
@@ -2713,6 +2745,7 @@ bool Http2Session::AddSettings(Http2Session::Http2Settings* settings) {
2713
2745
if (outstanding_settings_.size () == max_outstanding_settings_)
2714
2746
return false ;
2715
2747
outstanding_settings_.push (settings);
2748
+ IncrementCurrentSessionMemory (settings->self_size ());
2716
2749
return true ;
2717
2750
}
2718
2751
0 commit comments