1 // Copyright (C) 2016-2021 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 
7 #ifndef HTTP_RESPONSE_H
8 #define HTTP_RESPONSE_H
9 
10 #include <cc/data.h>
11 #include <http/header_context.h>
12 #include <http/http_message.h>
13 #include <http/response_context.h>
14 #include <boost/lexical_cast.hpp>
15 #include <boost/shared_ptr.hpp>
16 #include <string>
17 #include <vector>
18 
19 namespace isc {
20 namespace http {
21 
22 /// @brief Generic exception thrown by @ref HttpResponse class.
23 class HttpResponseError : public HttpMessageError {
24 public:
HttpResponseError(const char * file,size_t line,const char * what)25     HttpResponseError(const char* file, size_t line, const char* what) :
26         HttpMessageError(file, line, what) { };
27 };
28 
29 /// @brief HTTP status codes (cf RFC 2068)
30 enum class HttpStatusCode : std::uint16_t {
31     OK = 200,
32     CREATED = 201,
33     ACCEPTED = 202,
34     NO_CONTENT = 204,
35     MULTIPLE_CHOICES = 300,
36     MOVED_PERMANENTLY = 301,
37     MOVED_TEMPORARILY = 302,
38     NOT_MODIFIED = 304,
39     BAD_REQUEST = 400,
40     UNAUTHORIZED = 401,
41     FORBIDDEN = 403,
42     NOT_FOUND = 404,
43     REQUEST_TIMEOUT = 408,
44     INTERNAL_SERVER_ERROR = 500,
45     NOT_IMPLEMENTED = 501,
46     BAD_GATEWAY = 502,
47     SERVICE_UNAVAILABLE = 503
48 };
49 
50 /// @brief Encapsulates the boolean value indicating if the @ref HttpResponse
51 /// constructor should call its @c setGenericBody method during construction.
52 struct CallSetGenericBody {
53 
54     /// @brief Constructor.
55     ///
56     /// @param set A boolean value indicating if the method should be called
57     /// or not.
CallSetGenericBodyCallSetGenericBody58     explicit CallSetGenericBody(const bool set)
59         : set_(set) {
60     }
61 
62     /// @brief Returns encapsulated true.
yesCallSetGenericBody63     static const CallSetGenericBody& yes() {
64         static CallSetGenericBody yes(true);
65         return (yes);
66     }
67 
68     /// @brief Returns encapsulated false.
noCallSetGenericBody69     static const CallSetGenericBody& no() {
70         static CallSetGenericBody no(false);
71         return (no);
72     }
73 
74     /// @brief A storage for the boolean flag.
75     bool set_;
76 };
77 
78 class HttpResponse;
79 
80 /// @brief Pointer to the @ref HttpResponse object.
81 typedef boost::shared_ptr<HttpResponse> HttpResponsePtr;
82 
83 /// @brief Pointer to the const @ref HttpResponse object.
84 typedef boost::shared_ptr<const HttpResponse> ConstHttpResponsePtr;
85 
86 /// @brief Represents HTTP response message.
87 ///
88 /// This derivation of the @c HttpMessage class is specialized to represent
89 /// HTTP responses. This class provides two constructors for creating an inbound
90 /// and outbound response instance respectively. This class is associated with
91 /// an instance of the @c HttpResponseContext, which is used to provide response
92 /// specific values, such as HTTP status and headers.
93 ///
94 /// The derivations of this class provide specializations and specify the HTTP
95 /// versions and headers supported/required in the specific use cases. For example,
96 /// the @c HttpResponseJson class derives from the @c HttpResponse and it requires
97 /// that response includes a body in the JSON format.
98 class HttpResponse : public HttpMessage {
99 public:
100 
101     /// @brief Constructor for the inbound HTTP response.
102     explicit HttpResponse();
103 
104 
105     /// @brief Constructor for outbound HTTP response.
106     ///
107     /// Creates basic instance of the object. It sets the HTTP version and the
108     /// status code to be included in the response.
109     ///
110     /// @param version HTTP version.
111     /// @param status_code HTTP status code.
112     /// @param generic_body Indicates if the constructor should call
113     /// @c setGenericBody to create a generic content for the given
114     /// status code. This should be set to "no" when the constructor is
115     /// called by the derived class which provides its own implementation
116     /// of the @c setGenericBody method.
117     explicit HttpResponse(const HttpVersion& version,
118                           const HttpStatusCode& status_code,
119                           const CallSetGenericBody& generic_body =
120                           CallSetGenericBody::yes());
121 
122     /// @brief Returns pointer to the @ref HttpResponseContext.
123     ///
124     /// The context holds intermediate data for creating a response. The response
125     /// parser stores parsed raw data in the context. When parsing is finished,
126     /// the data are validated and committed into the @c HttpResponse.
127     ///
128     /// @return Pointer to the underlying @ref HttpResponseContext.
context()129     const HttpResponseContextPtr& context() const {
130         return (context_);
131     }
132 
133     /// @brief Commits information held in the context into the response.
134     ///
135     /// This function reads HTTP version, status code and headers from the
136     /// context and validates their values. For the outbound messages, it
137     /// automatically appends Content-Length and Date headers to the response.
138     /// The Content-Length is set to the body size. The Date is set to the
139     /// current date and time.
140     virtual void create();
141 
142     /// @brief Completes creation of the HTTP response.
143     ///
144     /// This method marks the response as finalized. The outbound response may now
145     /// be sent over the TCP socket. The information from the inbound message may
146     /// be read, including the response body.
147     virtual void finalize();
148 
149     /// @brief Reset the state of the object.
150     virtual void reset();
151 
152     /// @brief Returns HTTP status code.
153     HttpStatusCode getStatusCode() const;
154 
155     /// @brief Returns HTTP status phrase.
156     std::string getStatusPhrase() const;
157 
158     /// @brief Returns HTTP response body as string.
159     virtual std::string getBody() const;
160 
161     /// @brief Retrieves a single JSON element.
162     ///
163     /// The element must be at top level of the JSON structure.
164     ///
165     /// @param element_name Element name.
166     ///
167     /// @return Pointer to the specified element or NULL if such element
168     /// doesn't exist.
169     /// @throw HttpResponseJsonError if an error occurred.
170     data::ConstElementPtr getJsonElement(const std::string& element_name) const;
171 
172     /// @brief Checks if the status code indicates client error.
173     ///
174     /// @param status_code HTTP status code.
175     /// @return true if the status code indicates client error.
176     static bool isClientError(const HttpStatusCode& status_code);
177 
178     /// @brief Checks if the status code indicates server error.
179     ///
180     /// @param status_code HTTP status code.
181     /// @return true if the status code indicates server error.
182     static bool isServerError(const HttpStatusCode& status_code);
183 
184     /// @brief Converts status code to string.
185     ///
186     /// @param status_code HTTP status code.
187     /// @return Textual representation of the status code.
188     static std::string statusCodeToString(const HttpStatusCode& status_code);
189 
190     /// @brief Returns HTTP version and HTTP status as a string.
191     std::string toBriefString() const;
192 
193     /// @brief Returns HTTP response as string.
194     ///
195     /// This method is called to generate the outbound HTTP response. Make
196     /// sure to call @c finalize prior to calling this method.
197     virtual std::string toString() const;
198 
199 protected:
200 
201     /// @brief Returns current time formatted as required by RFC 1123.
202     ///
203     /// This method is virtual so as it can be overridden in unit tests
204     /// to return a "predictable" value of time, e.g. constant value.
205     ///
206     /// @return Current time formatted as required by RFC 1123.
207     virtual std::string getDateHeaderValue() const;
208 
209     /// @brief Convenience method converting status code to numeric value.
210     ///
211     /// @param status_code Status code represented as enum.
212     /// @return Numeric representation of the status code.
213     static uint16_t statusCodeToNumber(const HttpStatusCode& status_code);
214 
215 private:
216 
217     /// @brief Sets generic body for the given status code.
218     ///
219     /// Most of the classes derived from @ref HttpResponse will expect
220     /// a certain content type. Depending on the content type used they
221     /// will use different body formats for error messages. For example,
222     /// a response using text/html will use HTML within the response
223     /// body. The application/json will use JSON body etc. There is a
224     /// need to implement class specific way of generating the body
225     /// for error messages. Thus, each derivation of this class is
226     /// required to implement class specific @ref setGenericBody function
227     /// which should be called in the class constructor.
228     ///
229     /// This is also the case for this class, though the implementation
230     /// of @c setGenericBody is currently no-op.
231     ///
232     /// Note that this class can't be declared virtual because it is
233     /// meant to be called from the class constructor.
234     ///
235     /// @param status_code Status code for which the body should be
236     /// generated.
setGenericBody(const HttpStatusCode &)237     void setGenericBody(const HttpStatusCode& /*status_code*/) { };
238 
239 protected:
240 
241     /// @brief Pointer to the @ref HttpResponseContext holding parsed
242     /// data.
243     HttpResponseContextPtr context_;
244 };
245 
246 } // namespace http
247 } // namespace isc
248 
249 #endif
250