1 /*****************************************************************
3 | Platinum - HTTP Server Tasks
5 | Copyright (c) 2004-2010, Plutinosoft, LLC.
7 | http://www.plutinosoft.com
9 | This program is free software; you can redistribute it and/or
10 | modify it under the terms of the GNU General Public License
11 | as published by the Free Software Foundation; either version 2
12 | of the License, or (at your option) any later version.
14 | OEMs, ISVs, VARs and other distributors that combine and
15 | distribute commercially licensed software with Platinum software
16 | and do not wish to distribute the source code for the commercially
17 | licensed software under version 2, or (at your option) any later
18 | version, of the GNU General Public License (the "GPL") must enter
19 | into a commercial license agreement with Plutinosoft, LLC.
20 | licensing@plutinosoft.com
22 | This program is distributed in the hope that it will be useful,
23 | but WITHOUT ANY WARRANTY; without even the implied warranty of
24 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 | GNU General Public License for more details.
27 | You should have received a copy of the GNU General Public License
28 | along with this program; see the file LICENSE.txt. If not, write to
29 | the Free Software Foundation, Inc.,
30 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
31 | http://www.gnu.org/licenses/gpl-2.0.html
33 ****************************************************************/
35 /*----------------------------------------------------------------------
37 +---------------------------------------------------------------------*/
38 #include "PltHttpServerTask.h"
40 #include "PltVersion.h"
42 NPT_SET_LOCAL_LOGGER("platinum.core.http.servertask")
44 /*----------------------------------------------------------------------
46 +---------------------------------------------------------------------*/
47 extern NPT_String HttpServerHeader
;
48 const char* const PLT_HTTP_DEFAULT_403_HTML
= "<html><head><title>403 Forbidden</title></head><body><h1>Forbidden</h1><p>Access to this URL is forbidden.</p></html>";
49 const char* const PLT_HTTP_DEFAULT_404_HTML
= "<html><head><title>404 Not Found</title></head><body><h1>Not Found</h1><p>The requested URL was not found on this server.</p></html>";
50 const char* const PLT_HTTP_DEFAULT_500_HTML
= "<html><head><title>500 Internal Error</title></head><body><h1>Internal Error</h1><p>The server encountered an unexpected condition which prevented it from fulfilling the request.</p></html>";
52 /*----------------------------------------------------------------------
53 | PLT_HttpServerSocketTask::PLT_HttpServerSocketTask
54 +---------------------------------------------------------------------*/
55 PLT_HttpServerSocketTask::PLT_HttpServerSocketTask(NPT_Socket
* socket
,
56 bool stay_alive_forever
) :
58 m_StayAliveForever(stay_alive_forever
)
60 // needed for PS3 that is some case will request data every 35 secs and
61 // won't like it if server disconnected too early
62 m_Socket
->SetReadTimeout(60000);
63 m_Socket
->SetWriteTimeout(600000);
66 /*----------------------------------------------------------------------
67 | PLT_HttpServerSocketTask::~PLT_HttpServerSocketTask
68 +---------------------------------------------------------------------*/
69 PLT_HttpServerSocketTask::~PLT_HttpServerSocketTask()
77 /*----------------------------------------------------------------------
78 | PLT_HttpServerSocketTask::DoRun
79 +---------------------------------------------------------------------*/
81 PLT_HttpServerSocketTask::DoRun()
83 NPT_BufferedInputStreamReference buffered_input_stream
;
84 NPT_HttpRequestContext context
;
85 NPT_Result res
= NPT_SUCCESS
;
87 bool keep_alive
= false;
89 // create a buffered input stream to parse HTTP request
90 NPT_InputStreamReference input_stream
;
91 NPT_CHECK_LABEL_SEVERE(GetInputStream(input_stream
), done
);
92 NPT_CHECK_POINTER_LABEL_FATAL(input_stream
.AsPointer(), done
);
93 buffered_input_stream
= new NPT_BufferedInputStream(input_stream
);
95 while (!IsAborting(0)) {
96 NPT_HttpRequest
* request
= NULL
;
97 NPT_HttpResponse
* response
= NULL
;
99 // reset keep-alive to exit task on read failure
102 // wait for a request
103 res
= Read(buffered_input_stream
, request
, &context
);
104 if (NPT_FAILED(res
) || (request
== NULL
))
107 // process request and setup response
108 res
= RespondToClient(*request
, context
, response
);
109 if (NPT_FAILED(res
) || (response
== NULL
))
112 // check if client requested keep-alive
113 keep_alive
= PLT_HttpHelper::IsConnectionKeepAlive(*request
);
114 headers_only
= request
->GetMethod() == NPT_HTTP_METHOD_HEAD
;
116 // send response, pass keep-alive request from client
117 // (it can be overridden if response handler did not allow it)
118 res
= Write(response
, keep_alive
, headers_only
);
120 // on write error, reset keep_alive so we can close this connection
121 if (NPT_FAILED(res
)) keep_alive
= false;
128 if (!keep_alive
&& !m_StayAliveForever
) {
136 /*----------------------------------------------------------------------
137 | PLT_HttpServerSocketTask::GetInputStream
138 +---------------------------------------------------------------------*/
140 PLT_HttpServerSocketTask::GetInputStream(NPT_InputStreamReference
& stream
)
142 return m_Socket
->GetInputStream(stream
);
145 /*----------------------------------------------------------------------
146 | PLT_HttpServerSocketTask::GetInfo
147 +---------------------------------------------------------------------*/
149 PLT_HttpServerSocketTask::GetInfo(NPT_SocketInfo
& info
)
151 return m_Socket
->GetInfo(info
);
154 /*----------------------------------------------------------------------
155 | PLT_HttpServerSocketTask::Read
156 +---------------------------------------------------------------------*/
158 PLT_HttpServerSocketTask::Read(NPT_BufferedInputStreamReference
& buffered_input_stream
,
159 NPT_HttpRequest
*& request
,
160 NPT_HttpRequestContext
* context
)
165 // update context with socket info if needed
167 context
->SetLocalAddress(info
.local_address
);
168 context
->SetRemoteAddress(info
.remote_address
);
171 // put back in buffered mode to be able to parse HTTP request properly
172 buffered_input_stream
->SetBufferSize(NPT_BUFFERED_BYTE_STREAM_DEFAULT_SIZE
);
175 NPT_Result res
= NPT_HttpRequest::Parse(*buffered_input_stream
, &info
.local_address
, request
);
176 if (NPT_FAILED(res
) || !request
) {
177 // only log if not timeout
178 res
= NPT_FAILED(res
)?res
:NPT_FAILURE
;
179 if (res
!= NPT_ERROR_TIMEOUT
&& res
!= NPT_ERROR_EOS
) NPT_CHECK_WARNING(res
);
183 // update context with socket info again
184 // to refresh the remote address in case it was a non connected udp socket
187 context
->SetLocalAddress(info
.local_address
);
188 context
->SetRemoteAddress(info
.remote_address
);
191 // return right away if no body is expected
192 if (request
->GetMethod() == NPT_HTTP_METHOD_GET
||
193 request
->GetMethod() == NPT_HTTP_METHOD_HEAD
) {
198 NPT_HttpEntity
* request_entity
= new NPT_HttpEntity(request
->GetHeaders());
199 request
->SetEntity(request_entity
);
201 NPT_MemoryStream
* body_stream
= new NPT_MemoryStream();
202 request_entity
->SetInputStream((NPT_InputStreamReference
)body_stream
);
204 // unbuffer the stream to read body fast
205 buffered_input_stream
->SetBufferSize(0);
207 // check for chunked Transfer-Encoding
208 if (request_entity
->GetTransferEncoding() == "chunked") {
209 NPT_CHECK_SEVERE(NPT_StreamToStreamCopy(
210 *NPT_InputStreamReference(new NPT_HttpChunkedInputStream(buffered_input_stream
)).AsPointer(),
213 request_entity
->SetTransferEncoding(NULL
);
214 } else if (request_entity
->GetContentLength()) {
215 // a request with a body must always have a content length if not chunked
216 NPT_CHECK_SEVERE(NPT_StreamToStreamCopy(
217 *buffered_input_stream
.AsPointer(),
220 request_entity
->GetContentLength()));
222 request
->SetEntity(NULL
);
225 // rebuffer the stream
226 buffered_input_stream
->SetBufferSize(NPT_BUFFERED_BYTE_STREAM_DEFAULT_SIZE
);
231 /*----------------------------------------------------------------------
232 | PLT_HttpServerSocketTask::RespondToClient
233 +---------------------------------------------------------------------*/
235 PLT_HttpServerSocketTask::RespondToClient(NPT_HttpRequest
& request
,
236 const NPT_HttpRequestContext
& context
,
237 NPT_HttpResponse
*& response
)
239 NPT_Result result
= NPT_ERROR_NO_SUCH_ITEM
;
241 // reset output params first
244 // prepare the response body
245 NPT_HttpEntity
* body
= new NPT_HttpEntity();
246 response
= new NPT_HttpResponse(200, "OK", NPT_HTTP_PROTOCOL_1_1
);
247 response
->SetEntity(body
);
249 // ask to setup the response
250 result
= SetupResponse(request
, context
, *response
);
253 if (result
== NPT_ERROR_NO_SUCH_ITEM
) {
254 body
->SetInputStream(PLT_HTTP_DEFAULT_404_HTML
);
255 body
->SetContentType("text/html");
256 response
->SetStatus(404, "Not Found");
257 } else if (result
== NPT_ERROR_PERMISSION_DENIED
) {
258 body
->SetInputStream(PLT_HTTP_DEFAULT_403_HTML
);
259 body
->SetContentType("text/html");
260 response
->SetStatus(403, "Forbidden");
261 } else if (result
== NPT_ERROR_TERMINATED
) {
262 // mark that we want to exit
265 } else if (NPT_FAILED(result
)) {
266 body
->SetInputStream(PLT_HTTP_DEFAULT_500_HTML
);
267 body
->SetContentType("text/html");
268 response
->SetStatus(500, "Internal Error");
274 /*----------------------------------------------------------------------
275 | PLT_HttpServerSocketTask::SendResponseHeaders
276 +---------------------------------------------------------------------*/
278 PLT_HttpServerSocketTask::SendResponseHeaders(NPT_HttpResponse
* response
,
279 NPT_OutputStream
& output_stream
,
282 // add any headers that may be missing
283 NPT_HttpHeaders
& headers
= response
->GetHeaders();
285 // get the request entity to set additional headers
286 NPT_InputStreamReference body_stream
;
287 NPT_HttpEntity
* entity
= response
->GetEntity();
288 if (entity
&& NPT_SUCCEEDED(entity
->GetInputStream(body_stream
))) {
289 // set the content length if known
290 if (entity
->ContentLengthIsKnown()) {
291 headers
.SetHeader(NPT_HTTP_HEADER_CONTENT_LENGTH
,
292 NPT_String::FromIntegerU(entity
->GetContentLength()));
296 NPT_String content_type
= entity
->GetContentType();
297 if (!content_type
.IsEmpty()) {
298 headers
.SetHeader(NPT_HTTP_HEADER_CONTENT_TYPE
, content_type
);
302 NPT_String content_encoding
= entity
->GetContentEncoding();
303 if (!content_encoding
.IsEmpty()) {
304 headers
.SetHeader(NPT_HTTP_HEADER_CONTENT_ENCODING
, content_encoding
);
308 const NPT_String
& transfer_encoding
= entity
->GetTransferEncoding();
309 if (!transfer_encoding
.IsEmpty()) {
310 headers
.SetHeader(NPT_HTTP_HEADER_TRANSFER_ENCODING
, transfer_encoding
);
313 } else if (!headers
.GetHeader(NPT_HTTP_HEADER_CONTENT_LENGTH
)) {
314 // force content length to 0 if there is no message body
315 // (necessary for 1.1 or 1.0 with keep-alive connections)
316 headers
.SetHeader(NPT_HTTP_HEADER_CONTENT_LENGTH
, "0");
319 const NPT_String
* content_length
= headers
.GetHeaderValue(NPT_HTTP_HEADER_CONTENT_LENGTH
);
320 const NPT_String
* transfer_encoding
= headers
.GetHeaderValue(NPT_HTTP_HEADER_TRANSFER_ENCODING
);
321 const NPT_String
* connection_header
= headers
.GetHeaderValue(NPT_HTTP_HEADER_CONNECTION
);
323 if (connection_header
&& connection_header
->Compare("close") == 0) {
326 // the request says client supports keep-alive
327 // but override if response has content-length header or
328 // transfer chunked encoding
329 keep_alive
= content_length
||
330 (transfer_encoding
&& transfer_encoding
->Compare(NPT_HTTP_TRANSFER_ENCODING_CHUNKED
) == 0);
334 // only write keep-alive header for 1.1 if it's close
335 NPT_String protocol
= response
->GetProtocol();
336 if (protocol
.Compare(NPT_HTTP_PROTOCOL_1_0
, true) == 0 || !keep_alive
) {
337 headers
.SetHeader(NPT_HTTP_HEADER_CONNECTION
, keep_alive
?"keep-alive":"close", true);
339 headers
.SetHeader(NPT_HTTP_HEADER_SERVER
, PLT_HTTP_DEFAULT_SERVER
, false); // set but don't replace
341 PLT_LOG_HTTP_RESPONSE(NPT_LOG_LEVEL_FINE
, "PLT_HttpServerSocketTask::Write", response
);
343 // create a memory stream to buffer the headers
344 NPT_MemoryStream header_stream
;
345 response
->Emit(header_stream
);
348 NPT_CHECK_WARNING(output_stream
.WriteFully(header_stream
.GetData(), header_stream
.GetDataSize()));
353 /*----------------------------------------------------------------------
354 | PLT_HttpServerSocketTask::SendResponseBody
355 +---------------------------------------------------------------------*/
357 PLT_HttpServerSocketTask::SendResponseBody(NPT_HttpResponse
* response
,
358 NPT_OutputStream
& output_stream
)
360 NPT_HttpEntity
* entity
= response
->GetEntity();
361 if (!entity
) return NPT_SUCCESS
;
363 NPT_InputStreamReference body_stream
;
364 entity
->GetInputStream(body_stream
);
365 if (body_stream
.IsNull()) return NPT_SUCCESS
;
367 // check for chunked transfer encoding
368 NPT_OutputStream
* dest
= &output_stream
;
369 if (entity
->GetTransferEncoding() == NPT_HTTP_TRANSFER_ENCODING_CHUNKED
) {
370 dest
= new NPT_HttpChunkedOutputStream(output_stream
);
374 NPT_LOG_FINE_1("sending body stream, %lld bytes", entity
->GetContentLength());
375 NPT_LargeSize bytes_written
= 0;
376 NPT_Result result
= NPT_StreamToStreamCopy(*body_stream
, *dest
, 0, entity
->GetContentLength(), &bytes_written
); /* passing 0 if content length is unknown will read until nothing is left */
377 if (NPT_FAILED(result
)) {
378 NPT_LOG_FINE_3("body stream only partially sent, %lld bytes (%d:%s)",
381 NPT_ResultText(result
));
384 // flush to write out any buffered data left in chunked output if used
387 // cleanup (this will send zero size chunk followed by CRLF)
388 if (dest
!= &output_stream
) delete dest
;
393 /*----------------------------------------------------------------------
394 | PLT_HttpServerSocketTask::Write
395 +---------------------------------------------------------------------*/
397 PLT_HttpServerSocketTask::Write(NPT_HttpResponse
* response
,
399 bool headers_only
/* = false */)
401 // get the socket output stream
402 NPT_OutputStreamReference output_stream
;
403 NPT_CHECK_WARNING(m_Socket
->GetOutputStream(output_stream
));
406 NPT_CHECK_WARNING(SendResponseHeaders(response
, *output_stream
, keep_alive
));
410 NPT_CHECK_WARNING(SendResponseBody(response
, *output_stream
));
414 output_stream
->Flush();
419 /*----------------------------------------------------------------------
420 | PLT_HttpListenTask::DoRun
421 +---------------------------------------------------------------------*/
423 PLT_HttpListenTask::DoRun()
425 while (!IsAborting(0)) {
426 NPT_Socket
* client
= NULL
;
427 NPT_Result result
= m_Socket
->WaitForNewClient(client
, 5000, NPT_SOCKET_FLAG_CANCELLABLE
);
428 if (NPT_FAILED(result
)) {
429 // cleanup just in case
430 if (client
) delete client
;
433 if (result
== NPT_ERROR_TIMEOUT
) continue;
435 // exit on other errors ?
436 NPT_LOG_WARNING_2("PLT_HttpListenTask exiting with %d (%s)", result
, NPT_ResultText(result
));
439 PLT_ThreadTask
* task
= new PLT_HttpServerTask(m_Handler
, client
);
440 m_TaskManager
->StartTask(task
);