aboutsummaryrefslogtreecommitdiff
path: root/web/module.hxx
blob: a77164183691034d12ea2fc1e26328a7f517dbc4 (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
// file      : web/module.hxx -*- C++ -*-
// copyright : Copyright (c) 2014-2017 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.hxx>
#include <libbutl/optional.hxx>

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 module 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()?
    //
    // Throw invalid_request if decoding of any name or value fails.
    //
    virtual const name_values&
    parameters () = 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 (size_t limit = 0, 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 module::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 module can use it to log
  // diagnostics that is meant for the web server operator rather
  // than the user.
  //
  // The module 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 module instance for each request
  // by copy-initializing it with the module exemplar. This way we
  // achieve two things: we can freely use module 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 modules, 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 module
  {
  public:
    virtual
    ~module () = default;

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

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

    // During startup the web server calls this function on the module
    // 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 module 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