@@ -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 ();
@@ -673,18 +687,21 @@ inline bool Http2Session::CanAddStream() {
673
687
size_t maxSize =
674
688
std::min (streams_.max_size (), static_cast <size_t >(maxConcurrentStreams));
675
689
// We can add a new stream so long as we are less than the current
676
- // maximum on concurrent streams
677
- return streams_.size () < maxSize;
690
+ // maximum on concurrent streams and there's enough available memory
691
+ return streams_.size () < maxSize &&
692
+ IsAvailableSessionMemory (sizeof (Http2Stream));
678
693
}
679
694
680
695
inline void Http2Session::AddStream (Http2Stream* stream) {
681
696
CHECK_GE (++statistics_.stream_count , 0 );
682
697
streams_[stream->id ()] = stream;
698
+ IncrementCurrentSessionMemory (stream->self_size ());
683
699
}
684
700
685
701
686
- inline void Http2Session::RemoveStream (int32_t id) {
687
- streams_.erase (id);
702
+ inline void Http2Session::RemoveStream (Http2Stream* stream) {
703
+ streams_.erase (stream->id ());
704
+ DecrementCurrentSessionMemory (stream->self_size ());
688
705
}
689
706
690
707
// Used as one of the Padding Strategy functions. Will attempt to ensure
@@ -1678,7 +1695,7 @@ Http2Stream::Http2Stream(
1678
1695
1679
1696
Http2Stream::~Http2Stream () {
1680
1697
if (session_ != nullptr ) {
1681
- session_->RemoveStream (id_ );
1698
+ session_->RemoveStream (this );
1682
1699
session_ = nullptr ;
1683
1700
}
1684
1701
@@ -2008,7 +2025,7 @@ inline int Http2Stream::DoWrite(WriteWrap* req_wrap,
2008
2025
i == nbufs - 1 ? req_wrap : nullptr ,
2009
2026
bufs[i]
2010
2027
});
2011
- available_outbound_length_ += bufs[i].len ;
2028
+ IncrementAvailableOutboundLength ( bufs[i].len ) ;
2012
2029
}
2013
2030
CHECK_NE (nghttp2_session_resume_data (**session_, id_), NGHTTP2_ERR_NOMEM);
2014
2031
return 0 ;
@@ -2030,7 +2047,10 @@ inline bool Http2Stream::AddHeader(nghttp2_rcbuf* name,
2030
2047
if (this ->statistics_ .first_header == 0 )
2031
2048
this ->statistics_ .first_header = uv_hrtime ();
2032
2049
size_t length = GetBufferLength (name) + GetBufferLength (value) + 32 ;
2033
- if (current_headers_.size () == max_header_pairs_ ||
2050
+ // A header can only be added if we have not exceeded the maximum number
2051
+ // of headers and the session has memory available for it.
2052
+ if (!session_->IsAvailableSessionMemory (length) ||
2053
+ current_headers_.size () == max_header_pairs_ ||
2034
2054
current_headers_length_ + length > max_header_length_) {
2035
2055
return false ;
2036
2056
}
@@ -2174,7 +2194,7 @@ ssize_t Http2Stream::Provider::Stream::OnRead(nghttp2_session* handle,
2174
2194
// Just return the length, let Http2Session::OnSendData take care of
2175
2195
// actually taking the buffers out of the queue.
2176
2196
*flags |= NGHTTP2_DATA_FLAG_NO_COPY;
2177
- stream->available_outbound_length_ -= amount;
2197
+ stream->DecrementAvailableOutboundLength ( amount) ;
2178
2198
}
2179
2199
}
2180
2200
@@ -2197,6 +2217,15 @@ ssize_t Http2Stream::Provider::Stream::OnRead(nghttp2_session* handle,
2197
2217
return amount;
2198
2218
}
2199
2219
2220
+ inline void Http2Stream::IncrementAvailableOutboundLength (size_t amount) {
2221
+ available_outbound_length_ += amount;
2222
+ session_->IncrementCurrentSessionMemory (amount);
2223
+ }
2224
+
2225
+ inline void Http2Stream::DecrementAvailableOutboundLength (size_t amount) {
2226
+ available_outbound_length_ -= amount;
2227
+ session_->DecrementCurrentSessionMemory (amount);
2228
+ }
2200
2229
2201
2230
2202
2231
// Implementation of the JavaScript API
@@ -2690,6 +2719,7 @@ Http2Session::Http2Ping* Http2Session::PopPing() {
2690
2719
if (!outstanding_pings_.empty ()) {
2691
2720
ping = outstanding_pings_.front ();
2692
2721
outstanding_pings_.pop ();
2722
+ DecrementCurrentSessionMemory (ping->self_size ());
2693
2723
}
2694
2724
return ping;
2695
2725
}
@@ -2698,6 +2728,7 @@ bool Http2Session::AddPing(Http2Session::Http2Ping* ping) {
2698
2728
if (outstanding_pings_.size () == max_outstanding_pings_)
2699
2729
return false ;
2700
2730
outstanding_pings_.push (ping);
2731
+ IncrementCurrentSessionMemory (ping->self_size ());
2701
2732
return true ;
2702
2733
}
2703
2734
@@ -2706,6 +2737,7 @@ Http2Session::Http2Settings* Http2Session::PopSettings() {
2706
2737
if (!outstanding_settings_.empty ()) {
2707
2738
settings = outstanding_settings_.front ();
2708
2739
outstanding_settings_.pop ();
2740
+ DecrementCurrentSessionMemory (settings->self_size ());
2709
2741
}
2710
2742
return settings;
2711
2743
}
@@ -2714,6 +2746,7 @@ bool Http2Session::AddSettings(Http2Session::Http2Settings* settings) {
2714
2746
if (outstanding_settings_.size () == max_outstanding_settings_)
2715
2747
return false ;
2716
2748
outstanding_settings_.push (settings);
2749
+ IncrementCurrentSessionMemory (settings->self_size ());
2717
2750
return true ;
2718
2751
}
2719
2752
0 commit comments