summaryrefslogtreecommitdiffstats
path: root/src/shrpx_downstream.h
blob: 15f3a472dcca112b9ac29e373b69c2997c4979ee (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
/*
 * nghttp2 - HTTP/2 C Library
 *
 * Copyright (c) 2012 Tatsuhiro Tsujikawa
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#ifndef SHRPX_DOWNSTREAM_H
#define SHRPX_DOWNSTREAM_H

#include "shrpx.h"

#include <cinttypes>
#include <vector>
#include <string>
#include <memory>
#include <chrono>
#include <algorithm>

#include <ev.h>

#include <nghttp2/nghttp2.h>

#ifdef ENABLE_HTTP3
#  include <nghttp3/nghttp3.h>
#endif // ENABLE_HTTP3

#include "llhttp.h"

#include "shrpx_io_control.h"
#include "shrpx_log_config.h"
#include "http2.h"
#include "memchunk.h"
#include "allocator.h"

using namespace nghttp2;

namespace shrpx {

class Upstream;
class DownstreamConnection;
struct BlockedLink;
struct DownstreamAddrGroup;
struct DownstreamAddr;

class FieldStore {
public:
  FieldStore(BlockAllocator &balloc, size_t headers_initial_capacity)
      : content_length(-1),
        balloc_(balloc),
        buffer_size_(0),
        header_key_prev_(false),
        trailer_key_prev_(false) {
    headers_.reserve(headers_initial_capacity);
  }

  const HeaderRefs &headers() const { return headers_; }
  const HeaderRefs &trailers() const { return trailers_; }

  HeaderRefs &headers() { return headers_; }
  HeaderRefs &trailers() { return trailers_; }

  const void add_extra_buffer_size(size_t n) { buffer_size_ += n; }
  size_t buffer_size() const { return buffer_size_; }

  size_t num_fields() const { return headers_.size() + trailers_.size(); }

  // Returns pointer to the header field with the name |name|.  If
  // multiple header have |name| as name, return last occurrence from
  // the beginning.  If no such header is found, returns nullptr.
  const HeaderRefs::value_type *header(int32_t token) const;
  HeaderRefs::value_type *header(int32_t token);
  // Returns pointer to the header field with the name |name|.  If no
  // such header is found, returns nullptr.
  const HeaderRefs::value_type *header(const StringRef &name) const;

  void add_header_token(const StringRef &name, const StringRef &value,
                        bool no_index, int32_t token);

  // Adds header field name |name|.  First, the copy of header field
  // name pointed by name.c_str() of length name.size() is made, and
  // stored.
  void alloc_add_header_name(const StringRef &name);

  void append_last_header_key(const char *data, size_t len);
  void append_last_header_value(const char *data, size_t len);

  bool header_key_prev() const { return header_key_prev_; }

  // Parses content-length, and records it in the field.  If there are
  // multiple Content-Length, returns -1.
  int parse_content_length();

  // Empties headers.
  void clear_headers();

  void add_trailer_token(const StringRef &name, const StringRef &value,
                         bool no_index, int32_t token);

  // Adds trailer field name |name|.  First, the copy of trailer field
  // name pointed by name.c_str() of length name.size() is made, and
  // stored.
  void alloc_add_trailer_name(const StringRef &name);

  void append_last_trailer_key(const char *data, size_t len);
  void append_last_trailer_value(const char *data, size_t len);

  bool trailer_key_prev() const { return trailer_key_prev_; }

  // erase_content_length_and_transfer_encoding erases content-length
  // and transfer-encoding header fields.
  void erase_content_length_and_transfer_encoding();

  // content-length, -1 if it is unknown.
  int64_t content_length;

private:
  BlockAllocator &balloc_;
  HeaderRefs headers_;
  // trailer fields.  For HTTP/1.1, trailer fields are only included
  // with chunked encoding.  For HTTP/2, there is no such limit.
  HeaderRefs trailers_;
  // Sum of the length of name and value in headers_ and trailers_.
  // This could also be increased by add_extra_buffer_size() to take
  // into account for request URI in case of HTTP/1.x request.
  size_t buffer_size_;
  bool header_key_prev_;
  bool trailer_key_prev_;
};

// Protocols allowed in HTTP/2 :protocol header field.
enum class ConnectProto {
  NONE,
  WEBSOCKET,
};

struct Request {
  Request(BlockAllocator &balloc)
      : fs(balloc, 16),
        recv_body_length(0),
        unconsumed_body_length(0),
        method(-1),
        http_major(1),
        http_minor(1),
        connect_proto(ConnectProto::NONE),
        upgrade_request(false),
        http2_upgrade_seen(false),
        connection_close(false),
        http2_expect_body(false),
        no_authority(false),
        forwarded_once(false) {}

  void consume(size_t len) {
    assert(unconsumed_body_length >= len);
    unconsumed_body_length -= len;
  }

  bool regular_connect_method() const {
    return method == HTTP_CONNECT && connect_proto == ConnectProto::NONE;
  }

  bool extended_connect_method() const {
    return connect_proto != ConnectProto::NONE;
  }

  FieldStore fs;
  // Timestamp when all request header fields are received.
  std::shared_ptr<Timestamp> tstamp;
  // Request scheme.  For HTTP/2, this is :scheme header field value.
  // For HTTP/1.1, this is deduced from URI or connection.
  StringRef scheme;
  // Request authority.  This is HTTP/2 :authority header field value
  // or host header field value.  We may deduce it from absolute-form
  // HTTP/1 request.  We also store authority-form HTTP/1 request.
  // This could be empty if request comes from HTTP/1.0 without Host
  // header field and origin-form.
  StringRef authority;
  // Request path, including query component.  For HTTP/1.1, this is
  // request-target.  For HTTP/2, this is :path header field value.
  // For CONNECT request, this is empty.
  StringRef path;
  // This is original authority which cannot be changed by per-pattern
  // mruby script.
  StringRef orig_authority;
  // This is original path which cannot be changed by per-pattern
  // mruby script.
  StringRef orig_path;
  // the length of request body received so far
  int64_t recv_body_length;
  // The number of bytes not consumed by the application yet.
  size_t unconsumed_body_length;
  int method;
  // HTTP major and minor version
  int http_major, http_minor;
  // connect_proto specified in HTTP/2 :protocol pseudo header field
  // which enables extended CONNECT method.  This field is also set if
  // WebSocket upgrade is requested in h1 frontend for convenience.
  ConnectProto connect_proto;
  // Returns true if the request is HTTP upgrade (HTTP Upgrade or
  // CONNECT method).  Upgrade to HTTP/2 is excluded.  For HTTP/2
  // Upgrade, check get_http2_upgrade_request().
  bool upgrade_request;
  // true if h2c is seen in Upgrade header field.
  bool http2_upgrade_seen;
  bool connection_close;
  // true if this is HTTP/2, and request body is expected.  Note that
  // we don't take into account HTTP method here.
  bool http2_expect_body;
  // true if request does not have any information about authority.
  // This happens when: For HTTP/2 request, :authority is missing.
  // For HTTP/1 request, origin or asterisk form is used.
  bool no_authority;
  // true if backend selection is done for request once.
  // orig_authority and orig_path have the authority and path which
  // are used for the first backend selection.
  bool forwarded_once;
};

struct Response {
  Response(BlockAllocator &balloc)
      : fs(balloc, 32),
        recv_body_length(0),
        unconsumed_body_length(0),
        http_status(0),
        http_major(1),
        http_minor(1),
        connection_close(false),
        headers_only(false) {}

  void consume(size_t len) {
    assert(unconsumed_body_length >= len);
    unconsumed_body_length -= len;
  }

  // returns true if a resource denoted by scheme, authority, and path
  // has already been pushed.
  bool is_resource_pushed(const StringRef &scheme, const StringRef &authority,
                          const StringRef &path) const {
    if (!pushed_resources) {
      return false;
    }
    return std::find(std::begin(*pushed_resources), std::end(*pushed_resources),
                     std::make_tuple(scheme, authority, path)) !=
           std::end(*pushed_resources);
  }

  // remember that a resource denoted by scheme, authority, and path
  // is pushed.
  void resource_pushed(const StringRef &scheme, const StringRef &authority,
                       const StringRef &path) {
    if (!pushed_resources) {
      pushed_resources = std::make_unique<
          std::vector<std::tuple<StringRef, StringRef, StringRef>>>();
    }
    pushed_resources->emplace_back(scheme, authority, path);
  }

  FieldStore fs;
  // array of the tuple of scheme, authority, and path of pushed
  // resource.  This is required because RFC 8297 says that server
  // typically includes header fields appeared in non-final response
  // header fields in final response header fields.  Without checking
  // that a particular resource has already been pushed, or not, we
  // end up pushing the same resource at least twice.  It is unknown
  // that we should use more complex data structure (e.g., std::set)
  // to find the resources faster.
  std::unique_ptr<std::vector<std::tuple<StringRef, StringRef, StringRef>>>
      pushed_resources;
  // the length of response body received so far
  int64_t recv_body_length;
  // The number of bytes not consumed by the application yet.  This is
  // mainly for HTTP/2 backend.
  size_t unconsumed_body_length;
  // HTTP status code
  unsigned int http_status;
  int http_major, http_minor;
  bool connection_close;
  // true if response only consists of HEADERS, and it bears
  // END_STREAM.  This is used to tell Http2Upstream that it can send
  // response with single HEADERS with END_STREAM flag only.
  bool headers_only;
};

enum class DownstreamState {
  INITIAL,
  HEADER_COMPLETE,
  MSG_COMPLETE,
  STREAM_CLOSED,
  CONNECT_FAIL,
  MSG_RESET,
  // 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,
};

enum class DispatchState {
  NONE,
  PENDING,
  BLOCKED,
  ACTIVE,
  FAILURE,
};

class Downstream {
public:
  Downstream(Upstream *upstream, MemchunkPool *mcpool, int64_t stream_id);
  ~Downstream();
  void reset_upstream(Upstream *upstream);
  Upstream *get_upstream() const;
  void set_stream_id(int64_t stream_id);
  int64_t get_stream_id() const;
  void set_assoc_stream_id(int64_t stream_id);
  int64_t get_assoc_stream_id() const;
  void pause_read(IOCtrlReason reason);
  int resume_read(IOCtrlReason reason, size_t consumed);
  void force_resume_read();
  // Set stream ID for downstream HTTP2 connection.
  void set_downstream_stream_id(int64_t stream_id);
  int64_t get_downstream_stream_id() const;

  int attach_downstream_connection(std::unique_ptr<DownstreamConnection> dconn);
  void detach_downstream_connection();
  DownstreamConnection *get_downstream_connection();
  // Returns dconn_ and nullifies dconn_.
  std::unique_ptr<DownstreamConnection> pop_downstream_connection();

  // Returns true if output buffer is full. If underlying dconn_ is
  // NULL, this function always returns false.
  bool request_buf_full();
  // Returns true if upgrade (HTTP Upgrade or CONNECT) is succeeded in
  // h1 backend.  This should not depend on inspect_http1_response().
  void check_upgrade_fulfilled_http1();
  // Returns true if upgrade (HTTP Upgrade or CONNECT) is succeeded in
  // h2 backend.
  void check_upgrade_fulfilled_http2();
  // Returns true if the upgrade is succeeded as a result of the call
  // check_upgrade_fulfilled_http*().  HTTP/2 Upgrade is excluded.
  bool get_upgraded() const;
  // Inspects HTTP/2 request.
  void inspect_http2_request();
  // Inspects HTTP/1 request.  This checks whether the request is
  // upgrade request and tranfer-encoding etc.
  void inspect_http1_request();
  // Returns true if the request is HTTP Upgrade for HTTP/2
  bool get_http2_upgrade_request() const;
  // Returns the value of HTTP2-Settings request header field.
  StringRef get_http2_settings() const;

  // downstream request API
  const Request &request() const { return req_; }
  Request &request() { return req_; }

  // Count number of crumbled cookies
  size_t count_crumble_request_cookie();
  // Crumbles (split cookie by ";") in request_headers_ and adds them
  // in |nva|.  Headers::no_index is inherited.
  void crumble_request_cookie(std::vector<nghttp2_nv> &nva);
  // Assembles request cookies.  The opposite operation against
  // crumble_request_cookie().
  StringRef assemble_request_cookie();

  void
  set_request_start_time(std::chrono::high_resolution_clock::time_point time);
  const std::chrono::high_resolution_clock::time_point &
  get_request_start_time() const;
  int push_request_headers();
  bool get_chunked_request() const;
  void set_chunked_request(bool f);
  int push_upload_data_chunk(const uint8_t *data, size_t datalen);
  int end_upload_data();
  // Validates that received request body length and content-length
  // matches.
  bool validate_request_recv_body_length() const;
  void set_request_downstream_host(const StringRef &host);
  bool expect_response_body() const;
  bool expect_response_trailer() const;
  void set_request_state(DownstreamState state);
  DownstreamState get_request_state() const;
  DefaultMemchunks *get_request_buf();
  void set_request_pending(bool f);
  bool get_request_pending() const;
  void set_request_header_sent(bool f);
  bool get_request_header_sent() const;
  // Returns true if request is ready to be submitted to downstream.
  // When sending pending request, get_request_pending() should be
  // checked too because this function may return true when
  // get_request_pending() returns false.
  bool request_submission_ready() const;

  DefaultMemchunks *get_blocked_request_buf();
  bool get_blocked_request_data_eof() const;
  void set_blocked_request_data_eof(bool f);

  // downstream response API
  const Response &response() const { return resp_; }
  Response &response() { return resp_; }

  // Rewrites the location response header field.
  void rewrite_location_response_header(const StringRef &upstream_scheme);

  bool get_chunked_response() const;
  void set_chunked_response(bool f);

  void set_response_state(DownstreamState state);
  DownstreamState get_response_state() const;
  DefaultMemchunks *get_response_buf();
  bool response_buf_full();
  // Validates that received response body length and content-length
  // matches.
  bool validate_response_recv_body_length() const;
  uint32_t get_response_rst_stream_error_code() const;
  void set_response_rst_stream_error_code(uint32_t error_code);
  // Inspects HTTP/1 response.  This checks tranfer-encoding etc.
  void inspect_http1_response();
  // Clears some of member variables for response.
  void reset_response();
  // True if the response is non-final (1xx status code).  Note that
  // if connection was upgraded, 101 status code is treated as final.
  bool get_non_final_response() const;
  // True if protocol version used by client supports non final
  // response.  Only HTTP/1.1 and HTTP/2 clients support it.
  bool supports_non_final_response() const;
  void set_expect_final_response(bool f);
  bool get_expect_final_response() const;

  // Call this method when there is incoming data in downstream
  // connection.
  int on_read();

  void repeat_header_timer();
  void stop_header_timer();

  // Resets upstream read timer.  If it is active, timeout value is
  // reset.  If it is not active, timer will be started.
  void reset_upstream_rtimer();
  // Resets upstream write timer. If it is active, timeout value is
  // reset.  If it is not active, timer will be started.  This
  // function also resets read timer if it has been started.
  void reset_upstream_wtimer();
  // Makes sure that upstream write timer is started.  If it has been
  // started, do nothing.  Otherwise, write timer will be started.
  void ensure_upstream_wtimer();
  // Disables upstream read timer.
  void disable_upstream_rtimer();
  // Disables upstream write timer.
  void disable_upstream_wtimer();

  // Downstream timer functions.  They works in a similar way just
  // like the upstream timer function.
  void reset_downstream_rtimer();
  void reset_downstream_wtimer();
  void ensure_downstream_wtimer();
  void disable_downstream_rtimer();
  void disable_downstream_wtimer();

  // Returns true if accesslog can be written for this downstream.
  bool accesslog_ready() const;

  // Increment retry count
  void add_retry();
  // true if retry attempt should not be done.
  bool no_more_retry() const;

  DispatchState get_dispatch_state() const;
  void set_dispatch_state(DispatchState s);

  void attach_blocked_link(BlockedLink *l);
  BlockedLink *detach_blocked_link();

  // Returns true if downstream_connection can be detached and reused.
  bool can_detach_downstream_connection() const;

  DefaultMemchunks pop_response_buf();

  BlockAllocator &get_block_allocator();

  void add_rcbuf(nghttp2_rcbuf *rcbuf);
#ifdef ENABLE_HTTP3
  void add_rcbuf(nghttp3_rcbuf *rcbuf);
#endif // ENABLE_HTTP3

  void
  set_downstream_addr_group(const std::shared_ptr<DownstreamAddrGroup> &group);
  void set_addr(const DownstreamAddr *addr);

  const DownstreamAddr *get_addr() const;

  void set_accesslog_written(bool f);

  // Finds affinity cookie from request header fields.  The name of
  // cookie is given in |name|.  If an affinity cookie is found, it is
  // assigned to a member function, and is returned.  If it is not
  // found, or is malformed, returns 0.
  uint32_t find_affinity_cookie(const StringRef &name);
  // Set |h| as affinity cookie.
  void renew_affinity_cookie(uint32_t h);
  // Returns affinity cookie to send.  If it does not need to be sent,
  // for example, because the value is retrieved from a request header
  // field, returns 0.
  uint32_t get_affinity_cookie_to_send() const;

  void set_ws_key(const StringRef &key);

  bool get_expect_100_continue() const;

  bool get_stop_reading() const;
  void set_stop_reading(bool f);

  enum {
    EVENT_ERROR = 0x1,
    EVENT_TIMEOUT = 0x2,
  };

  Downstream *dlnext, *dlprev;

  // the length of response body sent to upstream client
  int64_t response_sent_body_length;

private:
  BlockAllocator balloc_;

  std::vector<nghttp2_rcbuf *> rcbufs_;
#ifdef ENABLE_HTTP3
  std::vector<nghttp3_rcbuf *> rcbufs3_;
#endif // ENABLE_HTTP3

  Request req_;
  Response resp_;

  std::chrono::high_resolution_clock::time_point request_start_time_;

  // host we requested to downstream.  This is used to rewrite
  // location header field to decide the location should be rewritten
  // or not.
  StringRef request_downstream_host_;

  // Data arrived in frontend before sending header fields to backend
  // are stored in this buffer.
  DefaultMemchunks blocked_request_buf_;
  DefaultMemchunks request_buf_;
  DefaultMemchunks response_buf_;

  // The Sec-WebSocket-Key field sent to the peer.  This field is used
  // if frontend uses RFC 8441 WebSocket bootstrapping via HTTP/2.
  StringRef ws_key_;

  ev_timer header_timer_;

  ev_timer upstream_rtimer_;
  ev_timer upstream_wtimer_;

  ev_timer downstream_rtimer_;
  ev_timer downstream_wtimer_;

  Upstream *upstream_;
  std::unique_ptr<DownstreamConnection> dconn_;

  // only used by HTTP/2 upstream
  BlockedLink *blocked_link_;
  // The backend address used to fulfill this request.  These are for
  // logging purpose.
  std::shared_ptr<DownstreamAddrGroup> group_;
  const DownstreamAddr *addr_;
  // How many times we tried in backend connection
  size_t num_retry_;
  // The stream ID in frontend connection
  int64_t stream_id_;
  // The associated stream ID in frontend connection if this is pushed
  // stream.
  int64_t assoc_stream_id_;
  // stream ID in backend connection
  int64_t downstream_stream_id_;
  // RST_STREAM error_code from downstream HTTP2 connection
  uint32_t response_rst_stream_error_code_;
  // An affinity cookie value.
  uint32_t affinity_cookie_;
  // request state
  DownstreamState request_state_;
  // response state
  DownstreamState response_state_;
  // only used by HTTP/2 upstream
  DispatchState dispatch_state_;
  // true if the connection is upgraded (HTTP Upgrade or CONNECT),
  // excluding upgrade to HTTP/2.
  bool upgraded_;
  // true if backend request uses chunked transfer-encoding
  bool chunked_request_;
  // true if response to client uses chunked transfer-encoding
  bool chunked_response_;
  // true if we have not got final response code
  bool expect_final_response_;
  // true if downstream request is pending because backend connection
  // has not been established or should be checked before use;
  // currently used only with HTTP/2 connection.
  bool request_pending_;
  // true if downstream request header is considered to be sent.
  bool request_header_sent_;
  // true if access.log has been written.
  bool accesslog_written_;
  // true if affinity cookie is generated for this request.
  bool new_affinity_cookie_;
  // true if eof is received from client before sending header fields
  // to backend.
  bool blocked_request_data_eof_;
  // true if request contains "expect: 100-continue" header field.
  bool expect_100_continue_;
  bool stop_reading_;
};

} // namespace shrpx

#endif // SHRPX_DOWNSTREAM_H