aboutsummaryrefslogtreecommitdiff
path: root/web/module
blob: 3e97ff7c341ad44f40516954f80ee9414323896f (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
// file      : web/module -*- C++ -*-
// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC
// license   : MIT; see accompanying LICENSE file

#ifndef WEB_MODULE
#define WEB_MODULE

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

namespace web
{
  // 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 description 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. If description is not empty, then it is assumed to be
  // encoded in UTF-8.
  //
  struct invalid_request
  {
    status_code status;
    std::string description;

    //@@ Maybe optional "try again" link?
    //
    invalid_request (status_code s = 400, std::string d = "")
        : status (s), description (std::move (d)) {}
  };

  // 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)) {}
  };

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

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

  using name_values = std::vector<name_value>;

  class request
  {
  public:
    //@@ 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:
    // 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, const std::string& type, 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 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:
    // 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 void
    handle (request&, response&, log&) = 0;
  };
}

#endif // WEB_MODULE