aboutsummaryrefslogtreecommitdiff
path: root/web/module
blob: 9f1c7783cf7a5125edc1234efdc2627533988802 (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
// 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 <utility>   // move()
#include <stdexcept> // runtime_error
#include <string>
#include <vector>
#include <iosfwd>
#include <chrono>
#include <cstdint>   // uint16_t

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.
  // Non empty description of a caught by the module implementation exception
  // can be sent to client in http response body with
  // Content-Type:text/html;charset=utf-8 header.
  //
  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 (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 mime url decode of name or value fail.
    //
    virtual const name_values&
    parameters () = 0;

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

    // Get stream to read request body data.
    // Throw sequence_error if some unbuffered content is already written.
    //
    virtual std::istream&
    data () = 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 discards buffered output and throw sequence_error
    // if output were not buffered.
    //
    virtual void
    status (status_code) = 0;

    // Throw sequence_error if some unbuffered content is already written as
    // will not be able to send Set-Cookie header.
    //
    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:
    virtual void
    handle (request&, response&, log&) = 0;
  };
}

#endif // WEB_MODULE