aboutsummaryrefslogtreecommitdiff
path: root/web/module.hxx
blob: 73ffcafc166d1170946493b6b9338bacf0faabdd (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
// file      : web/module.hxx -*- C++ -*-
// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
// license   : MIT; see accompanying LICENSE file

#ifndef WEB_MODULE_HXX
#define WEB_MODULE_HXX

#include <map>
#include <string>
#include <vector>
#include <iosfwd>
#include <chrono>
#include <cstdint>   // uint16_t
#include <cstddef>   // size_t
#include <utility>   // move()
#include <stdexcept> // runtime_error

#include <libbutl/path.mxx>
#include <libbutl/optional.mxx>

namespace web
{
  using butl::optional;

  // HTTP status code.
  //
  // @@ Define some commonly used constants?
  //
  using status_code = std::uint16_t;

  // This exception is used to signal that the request is invalid
  // (4XX codes) rather than that it could not be processed (5XX).
  // By default 400 is returned, which means the request is malformed.
  //
  // If caught by the web server implementation, it will try to return
  // the specified status and content to the client, if possible.
  // It is, however, may not be possible if some unbuffered content has
  // already been written. The behavior in this case is implementation-
  // specific and may result in no indication of an error being sent to
  // the client.
  //
  struct invalid_request
  {
    status_code status;
    std::string content;
    std::string type;

    //@@ Maybe optional "try again" link?
    //
    invalid_request (status_code s = 400,
                     std::string c = "",
                     std::string t = "text/plain;charset=utf-8")
        : status (s), content (std::move (c)), type (std::move (t)) {}
  };

  // Exception indicating HTTP request/response sequencing error.
  // For example, trying to change the status code after some
  // content has already been written.
  //
  struct sequence_error: std::runtime_error
  {
    sequence_error (std::string d): std::runtime_error (std::move (d)) {}
  };

  // Map of handler configuration option names to the boolean flag indicating
  // whether the value is expected for the option.
  //
  using option_descriptions = std::map<std::string, bool>;

  struct name_value
  {
    // These should eventually become string_view's.
    //
    std::string name;
    optional<std::string> value;

    name_value () {}
    name_value (std::string n, optional<std::string> v)
        : name (std::move (n)), value (std::move (v)) {}
  };

  using name_values = std::vector<name_value>;
  using butl::path;

  class request
  {
  public:
    using path_type = web::path;

    virtual
    ~request () = default;

    // Corresponds to abs_path portion of HTTP URL as described in "3.2.2 HTTP
    // URL" of http://tools.ietf.org/html/rfc2616. Returns '/' if no abs_path
    // is present in URL.
    //
    virtual const path_type&
    path () = 0;

    //@@ Why not pass parameters directly? Lazy parsing?
    //@@ Why not have something like operator[] for lookup? Probably
    //   in name_values.
    //@@ Maybe parameter_list() and parameter_map()?
    //
    // Parse parameters from the URL query part and from the HTTP POST request
    // body for the application/x-www-form-urlencoded or multipart/form-data
    // content type. Optionally limit the amount of data read from the body
    // (see the content() function for the semantics). Throw invalid_request
    // if parameters decoding fails.
    //
    virtual const name_values&
    parameters (std::size_t limit, bool url_only = false) = 0;

    // Open the input stream for the upload corresponding to the specified
    // parameter index. Must be called after the parameters() function is
    // called, throw sequence_error if that's not the case. Throw
    // invalid_argument if the index doesn't have an upload (for example,
    // because the parameter is not <input type="file"/> form field).
    //
    // Note also that reopening the same upload (within the same retry)
    // returns the same stream reference.
    //
    virtual std::istream&
    open_upload (std::size_t index) = 0;

    // As above but specify the parameter by name. Throw invalid_argument if
    // there are multiple uploads for this parameter name.
    //
    virtual std::istream&
    open_upload (const std::string& name) = 0;

    // Request headers.
    //
    // The implementation may add custom pseudo-headers reflecting additional
    // request options. Such headers should start with ':'. If possible, the
    // implementation should add the following well-known pseudo-headers:
    //
    // :Client-IP - IP address of the connecting client.
    //
    virtual const name_values&
    headers () = 0;

    // Throw invalid_request if cookies are malformed.
    //
    virtual const name_values&
    cookies () = 0;

    // Get the stream to read the request content from. If the limit argument
    // is zero, then the content limit is left unchanged (unlimited initially).
    // Otherwise the requested limit is set, and the invalid_request exception
    // with the code 413 (payload too large) will be thrown when the specified
    // limit is reached while reading from the stream. If the buffer argument
    // is zero, then the buffer size is left unchanged (zero initially). If it
    // is impossible to increase the buffer size (because, for example, some
    // content is already read unbuffered), then the sequence_error is thrown.
    //
    // Note that unread input content is discarded when any unbuffered content
    // is written, and any attempt to read it will result in the
    // sequence_error exception being thrown.
    //
    virtual std::istream&
    content (std::size_t limit, std::size_t buffer = 0) = 0;
  };

  class response
  {
  public:
    virtual
    ~response () = default;

    // Set status code, content type, and get the stream to write
    // the content to. If the buffer argument is true (default),
    // then buffer the entire content before sending it as a
    // response. This allows us to change the status code in
    // case of an error.
    //
    // Specifically, if there is already content in the buffer
    // and the status code is changed, then the old content is
    // discarded. If the content was not buffered and the status
    // is changed, then the sequence_error exception is thrown.
    // If this exception leaves handler::handle(), then the
    // implementation shall terminate the response in a suitable
    // but unspecified manner. In particular, there is no guarantee
    // that the user will be notified of an error or observe the
    // new status.
    //
    virtual std::ostream&
    content (status_code code = 200,
             const std::string& type = "application/xhtml+xml;charset=utf-8",
             bool buffer = true) = 0;

    // Set status code without writing any content. On status change,
    // discard buffered content or throw sequence_error if content was
    // not buffered.
    //
    virtual void
    status (status_code) = 0;

    // Throw sequence_error if some unbuffered content has already
    // been written.
    //
    virtual void
    cookie (const char* name,
            const char* value,
            const std::chrono::seconds* max_age = nullptr,
            const char* path = nullptr,
            const char* domain = nullptr,
            bool secure = false,
            bool buffer = true) = 0;
  };

  // A web server logging backend. The handler can use it to log
  // diagnostics that is meant for the web server operator rather
  // than the user.
  //
  // The handler can cast this basic interface to the web server's
  // specific implementation that may provide a richer interface.
  //
  class log
  {
  public:
    virtual
    ~log () = default;

    virtual void
    write (const char* msg) = 0;
  };

  // The web server creates a new handler instance for each request
  // by copy-initializing it with the handler exemplar. This way we
  // achieve two things: we can freely use handler data members
  // without worrying about multi-threading issues and we
  // automatically get started with the initial state for each
  // request. If you really need to share some rw-data between
  // all the handlers, use static data members with appropriate
  // locking. See the <service> header in one of the web server
  // directories (e.g., apache/) if you need to see the code that
  // does this.
  //
  class handler
  {
  public:
    virtual
    ~handler () = default;

    // Description of configuration options supported by this handler. Note:
    // should be callable during static initialization.
    //
    virtual option_descriptions
    options () = 0;

    // During startup the web server calls this function on the handler
    // exemplar to log the handler version information. It is up to the web
    // server whether to call this function once per handler implementation
    // type. Therefore, it is expected that this function will log the same
    // information for all the handler exemplars.
    //
    virtual void
    version (log&) = 0;

    // During startup the web server calls this function on the handler
    // exemplar passing a list of configuration options. The place these
    // configuration options come from is implementation-specific (normally
    // a configuration file). The web server guarantees that only options
    // listed in the map returned by the options() function above can be
    // present. Any exception thrown by this function terminates the web
    // server.
    //
    virtual void
    init (const name_values&, log&) = 0;

    // Return false if decline to handle the request. If handling have been
    // declined after any unbuffered content has been written, then the
    // implementation shall terminate the response in a suitable but
    // unspecified manner.
    //
    // Throw retry if need to retry handling the request. The retry will
    // happen on the same instance of the handler and the implementation is
    // expected to "rewind" the request and response objects to their initial
    // state. This is only guaranteed to be possible if the relevant functions
    // in the request and response objects were called in buffered mode (the
    // buffer argument was true).
    //
    // Any exception other than retry and invalid_request described above that
    // leaves this function is treated by the web server implementation as an
    // internal server error (500). Similar to invalid_request, it will try to
    // return the status and description (obtained by calling what() on
    // std::exception) to the client, if possible. The description is assume
    // to be encoded in UTF-8. The implementation may provide a configuration
    // option to omit the description from the response, for security/privacy
    // reasons.
    //
    struct retry {};

    virtual bool
    handle (request&, response&, log&) = 0;
  };
}

#endif // WEB_MODULE_HXX