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

#ifndef WEB_MODULE
#define WEB_MODULE

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

#include <butl/path>
#include <butl/optional>

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. Note that
    // reading content after any unbuffered content has been written
    // is undefined behavior. The implementation may detect it and
    // throw sequence_error but is not required to do so.
    //
    virtual std::istream&
    content () = 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 = 0,
            const char* path = 0,
            const char* domain = 0,
            bool secure = false) = 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. Any exception other than 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.
    //
    virtual bool
    handle (request&, response&, log&) = 0;
  };
}

#endif // WEB_MODULE