1 /*****************************************************************
3 | Neptune - HTTP Protocol
5 | Copyright (c) 2002-2008, Axiomatic Systems, LLC.
8 | Redistribution and use in source and binary forms, with or without
9 | modification, are permitted provided that the following conditions are met:
10 | * Redistributions of source code must retain the above copyright
11 | notice, this list of conditions and the following disclaimer.
12 | * Redistributions in binary form must reproduce the above copyright
13 | notice, this list of conditions and the following disclaimer in the
14 | documentation and/or other materials provided with the distribution.
15 | * Neither the name of Axiomatic Systems nor the
16 | names of its contributors may be used to endorse or promote products
17 | derived from this software without specific prior written permission.
19 | THIS SOFTWARE IS PROVIDED BY AXIOMATIC SYSTEMS ''AS IS'' AND ANY
20 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 | DISCLAIMED. IN NO EVENT SHALL AXIOMATIC SYSTEMS BE LIABLE FOR ANY
23 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 ****************************************************************/
32 /*----------------------------------------------------------------------
34 +---------------------------------------------------------------------*/
36 #include "NptSockets.h"
37 #include "NptBufferedStreams.h"
39 #include "NptVersion.h"
42 #include "NptSystem.h"
43 #include "NptLogging.h"
45 #include "NptStreams.h"
47 /*----------------------------------------------------------------------
49 +---------------------------------------------------------------------*/
50 NPT_SET_LOCAL_LOGGER("neptune.http")
52 /*----------------------------------------------------------------------
54 +---------------------------------------------------------------------*/
55 const char* const NPT_HTTP_DEFAULT_403_HTML
= "<html><head><title>403 Forbidden</title></head><body><h1>Forbidden</h1><p>Access to this URL is forbidden.</p></html>";
56 const char* const NPT_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>";
57 const char* const NPT_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>";
59 /*----------------------------------------------------------------------
60 | NPT_HttpUrl::NPT_HttpUrl
61 +---------------------------------------------------------------------*/
62 NPT_HttpUrl::NPT_HttpUrl(const char* url
, bool ignore_scheme
) :
66 if (GetSchemeId() != NPT_Uri::SCHEME_ID_HTTP
&&
67 GetSchemeId() != NPT_Uri::SCHEME_ID_HTTPS
) {
73 /*----------------------------------------------------------------------
74 | NPT_HttpUrl::NPT_HttpUrl
75 +---------------------------------------------------------------------*/
76 NPT_HttpUrl::NPT_HttpUrl(const char* host
,
80 const char* fragment
) :
81 NPT_Url("http", host
, port
, path
, query
, fragment
)
85 /*----------------------------------------------------------------------
86 | NPT_HttpUrl::ToString
87 +---------------------------------------------------------------------*/
89 NPT_HttpUrl::ToString(bool with_fragment
) const
91 NPT_UInt16 default_port
;
93 case SCHEME_ID_HTTP
: default_port
= NPT_HTTP_DEFAULT_PORT
; break;
94 case SCHEME_ID_HTTPS
: default_port
= NPT_HTTPS_DEFAULT_PORT
; break;
95 default: default_port
= 0;
97 return NPT_Url::ToStringWithDefaultPort(default_port
, with_fragment
);
100 /*----------------------------------------------------------------------
101 | NPT_HttpHeader::NPT_HttpHeader
102 +---------------------------------------------------------------------*/
103 NPT_HttpHeader::NPT_HttpHeader(const char* name
, const char* value
):
109 /*----------------------------------------------------------------------
110 | NPT_HttpHeader::~NPT_HttpHeader
111 +---------------------------------------------------------------------*/
112 NPT_HttpHeader::~NPT_HttpHeader()
116 /*----------------------------------------------------------------------
117 | NPT_HttpHeader::Emit
118 +---------------------------------------------------------------------*/
120 NPT_HttpHeader::Emit(NPT_OutputStream
& stream
) const
122 stream
.WriteString(m_Name
);
123 stream
.WriteFully(": ", 2);
124 stream
.WriteString(m_Value
);
125 stream
.WriteFully(NPT_HTTP_LINE_TERMINATOR
, 2);
126 NPT_LOG_FINEST_2("header %s: %s", m_Name
.GetChars(), m_Value
.GetChars());
131 /*----------------------------------------------------------------------
132 | NPT_HttpHeader::SetName
133 +---------------------------------------------------------------------*/
135 NPT_HttpHeader::SetName(const char* name
)
141 /*----------------------------------------------------------------------
142 | NPT_HttpHeader::~NPT_HttpHeader
143 +---------------------------------------------------------------------*/
145 NPT_HttpHeader::SetValue(const char* value
)
151 /*----------------------------------------------------------------------
152 | NPT_HttpHeaders::NPT_HttpHeaders
153 +---------------------------------------------------------------------*/
154 NPT_HttpHeaders::NPT_HttpHeaders()
158 /*----------------------------------------------------------------------
159 | NPT_HttpHeaders::~NPT_HttpHeaders
160 +---------------------------------------------------------------------*/
161 NPT_HttpHeaders::~NPT_HttpHeaders()
163 m_Headers
.Apply(NPT_ObjectDeleter
<NPT_HttpHeader
>());
166 /*----------------------------------------------------------------------
167 | NPT_HttpHeaders::Parse
168 +---------------------------------------------------------------------*/
170 NPT_HttpHeaders::Parse(NPT_BufferedInputStream
& stream
)
172 NPT_String header_name
;
173 NPT_String header_value
;
174 bool header_pending
= false;
177 while (NPT_SUCCEEDED(stream
.ReadLine(line
, NPT_HTTP_PROTOCOL_MAX_LINE_LENGTH
))) {
178 if (line
.GetLength() == 0) {
179 // empty line, end of headers
182 if (header_pending
&& (line
[0] == ' ' || line
[0] == '\t')) {
183 // continuation (folded header)
184 header_value
.Append(line
.GetChars()+1, line
.GetLength()-1);
186 // add the pending header to the list
187 if (header_pending
) {
189 AddHeader(header_name
, header_value
);
190 header_pending
= false;
191 NPT_LOG_FINEST_2("header - %s: %s",
192 header_name
.GetChars(),
193 header_value
.GetChars());
196 // find the colon separating the name and the value
197 int colon_index
= line
.Find(':');
198 if (colon_index
< 1) {
199 // invalid syntax, ignore
202 header_name
= line
.Left(colon_index
);
204 // the field value starts at the first non-whitespace
205 const char* value
= line
.GetChars()+colon_index
+1;
206 while (*value
== ' ' || *value
== '\t') {
209 header_value
= value
;
211 // the header is pending
212 header_pending
= true;
216 // if we have a header pending, add it now
217 if (header_pending
) {
219 AddHeader(header_name
, header_value
);
220 NPT_LOG_FINEST_2("header %s: %s",
221 header_name
.GetChars(),
222 header_value
.GetChars());
228 /*----------------------------------------------------------------------
229 | NPT_HttpHeaders::Emit
230 +---------------------------------------------------------------------*/
232 NPT_HttpHeaders::Emit(NPT_OutputStream
& stream
) const
234 // for each header in the list
235 NPT_List
<NPT_HttpHeader
*>::Iterator header
= m_Headers
.GetFirstItem();
238 NPT_CHECK_WARNING((*header
)->Emit(stream
));
244 /*----------------------------------------------------------------------
245 | NPT_HttpHeaders::GetHeader
246 +---------------------------------------------------------------------*/
248 NPT_HttpHeaders::GetHeader(const char* name
) const
251 if (name
== NULL
) return NULL
;
253 // find a matching header
254 NPT_List
<NPT_HttpHeader
*>::Iterator header
= m_Headers
.GetFirstItem();
256 if ((*header
)->GetName().Compare(name
, true) == 0) {
266 /*----------------------------------------------------------------------
267 | NPT_HttpHeaders::AddHeader
268 +---------------------------------------------------------------------*/
270 NPT_HttpHeaders::AddHeader(const char* name
, const char* value
)
272 return m_Headers
.Add(new NPT_HttpHeader(name
, value
));
275 /*----------------------------------------------------------------------
276 | NPT_HttpHeaders::RemoveHeader
277 +---------------------------------------------------------------------*/
279 NPT_HttpHeaders::RemoveHeader(const char* name
)
283 NPT_HttpHeader
* header
= NULL
;
284 while ((header
= GetHeader(name
))) {
285 m_Headers
.Remove(header
);
289 return found
?NPT_SUCCESS
:NPT_ERROR_NO_SUCH_ITEM
;
292 /*----------------------------------------------------------------------
293 | NPT_HttpHeaders::SetHeader
294 +---------------------------------------------------------------------*/
296 NPT_HttpHeaders::SetHeader(const char* name
, const char* value
, bool replace
)
298 NPT_HttpHeader
* header
= GetHeader(name
);
299 if (header
== NULL
) {
300 return AddHeader(name
, value
);
301 } else if (replace
) {
302 return header
->SetValue(value
);
308 /*----------------------------------------------------------------------
309 | NPT_HttpHeaders::GetHeaderValue
310 +---------------------------------------------------------------------*/
312 NPT_HttpHeaders::GetHeaderValue(const char* name
) const
314 NPT_HttpHeader
* header
= GetHeader(name
);
315 if (header
== NULL
) {
318 return &header
->GetValue();
322 /*----------------------------------------------------------------------
323 | NPT_HttpEntityBodyInputStream
324 +---------------------------------------------------------------------*/
325 class NPT_HttpEntityBodyInputStream
: public NPT_InputStream
328 // constructor and desctructor
329 NPT_HttpEntityBodyInputStream(NPT_BufferedInputStreamReference
& source
,
333 NPT_HttpClient::Connection
* connection
,
334 bool should_persist
);
335 ~NPT_HttpEntityBodyInputStream() override
;
338 bool SizeIsKnown() { return m_SizeIsKnown
; }
340 // NPT_InputStream methods
341 NPT_Result
Read(void* buffer
,
342 NPT_Size bytes_to_read
,
343 NPT_Size
* bytes_read
= NULL
) override
;
344 NPT_Result
Seek(NPT_Position
/*offset*/) override
{
345 return NPT_ERROR_NOT_SUPPORTED
;
347 NPT_Result
Tell(NPT_Position
& offset
) override
{
351 NPT_Result
GetSize(NPT_LargeSize
& size
) override
{
355 NPT_Result
GetAvailable(NPT_LargeSize
& available
) override
;
359 virtual void OnFullyRead();
362 NPT_LargeSize m_Size
;
365 NPT_HttpClient::Connection
* m_Connection
;
366 bool m_ShouldPersist
;
367 NPT_Position m_Position
;
368 NPT_InputStreamReference m_Source
;
371 /*----------------------------------------------------------------------
372 | NPT_HttpEntityBodyInputStream::NPT_HttpEntityBodyInputStream
373 +---------------------------------------------------------------------*/
374 NPT_HttpEntityBodyInputStream::NPT_HttpEntityBodyInputStream(
375 NPT_BufferedInputStreamReference
& source
,
379 NPT_HttpClient::Connection
* connection
,
380 bool should_persist
) :
382 m_SizeIsKnown(size_is_known
),
384 m_Connection(connection
),
385 m_ShouldPersist(should_persist
),
388 if (size_is_known
&& size
== 0) {
392 m_Source
= NPT_InputStreamReference(new NPT_HttpChunkedInputStream(source
));
399 /*----------------------------------------------------------------------
400 | NPT_HttpEntityBodyInputStream::~NPT_HttpEntityBodyInputStream
401 +---------------------------------------------------------------------*/
402 NPT_HttpEntityBodyInputStream::~NPT_HttpEntityBodyInputStream()
407 /*----------------------------------------------------------------------
408 | NPT_HttpEntityBodyInputStream::OnFullyRead
409 +---------------------------------------------------------------------*/
411 NPT_HttpEntityBodyInputStream::OnFullyRead()
414 if (m_Connection
&& m_ShouldPersist
) {
415 m_Connection
->Recycle();
420 /*----------------------------------------------------------------------
421 | NPT_HttpEntityBodyInputStream::Read
422 +---------------------------------------------------------------------*/
424 NPT_HttpEntityBodyInputStream::Read(void* buffer
,
425 NPT_Size bytes_to_read
,
426 NPT_Size
* bytes_read
)
428 if (bytes_read
) *bytes_read
= 0;
430 // return now if we've already reached the end
431 if (m_Source
.IsNull()) return NPT_ERROR_EOS
;
433 // clamp to the max possible read size
434 if (!m_Chunked
&& m_SizeIsKnown
) {
435 NPT_LargeSize max_can_read
= m_Size
-m_Position
;
436 if (max_can_read
== 0) return NPT_ERROR_EOS
;
437 if (bytes_to_read
> max_can_read
) bytes_to_read
= (NPT_Size
)max_can_read
;
440 // read from the source
441 NPT_Size source_bytes_read
= 0;
442 NPT_Result result
= m_Source
->Read(buffer
, bytes_to_read
, &source_bytes_read
);
443 if (NPT_SUCCEEDED(result
)) {
444 m_Position
+= source_bytes_read
;
445 if (bytes_read
) *bytes_read
= source_bytes_read
;
448 // check if we've reached the end
449 if (result
== NPT_ERROR_EOS
|| (m_SizeIsKnown
&& (m_Position
== m_Size
))) {
456 /*----------------------------------------------------------------------
457 | NPT_HttpEntityBodyInputStream::GetAvaialble
458 +---------------------------------------------------------------------*/
460 NPT_HttpEntityBodyInputStream::GetAvailable(NPT_LargeSize
& available
)
462 if (m_Source
.IsNull()) {
466 NPT_Result result
= m_Source
->GetAvailable(available
);
467 if (NPT_FAILED(result
)) {
471 if (available
> m_Size
-m_Position
) {
472 available
= m_Size
-m_Position
;
477 /*----------------------------------------------------------------------
478 | NPT_HttpEntity::NPT_HttpEntity
479 +---------------------------------------------------------------------*/
480 NPT_HttpEntity::NPT_HttpEntity() :
482 m_ContentLengthIsKnown(false)
486 /*----------------------------------------------------------------------
487 | NPT_HttpEntity::NPT_HttpEntity
488 +---------------------------------------------------------------------*/
489 NPT_HttpEntity::NPT_HttpEntity(const NPT_HttpHeaders
& headers
) :
491 m_ContentLengthIsKnown(false)
496 /*----------------------------------------------------------------------
497 | NPT_HttpEntity::SetHeaders
498 +---------------------------------------------------------------------*/
500 NPT_HttpEntity::SetHeaders(const NPT_HttpHeaders
& headers
)
502 NPT_HttpHeader
* header
;
505 header
= headers
.GetHeader(NPT_HTTP_HEADER_CONTENT_LENGTH
);
506 if (header
!= NULL
) {
507 m_ContentLengthIsKnown
= true;
508 NPT_LargeSize length
;
509 if (NPT_SUCCEEDED(header
->GetValue().ToInteger64(length
))) {
510 m_ContentLength
= length
;
517 header
= headers
.GetHeader(NPT_HTTP_HEADER_CONTENT_TYPE
);
518 if (header
!= NULL
) {
519 m_ContentType
= header
->GetValue();
523 header
= headers
.GetHeader(NPT_HTTP_HEADER_CONTENT_ENCODING
);
524 if (header
!= NULL
) {
525 m_ContentEncoding
= header
->GetValue();
529 header
= headers
.GetHeader(NPT_HTTP_HEADER_TRANSFER_ENCODING
);
530 if (header
!= NULL
) {
531 m_TransferEncoding
= header
->GetValue();
537 /*----------------------------------------------------------------------
538 | NPT_HttpEntity::~NPT_HttpEntity
539 +---------------------------------------------------------------------*/
540 NPT_HttpEntity::~NPT_HttpEntity()
544 /*----------------------------------------------------------------------
545 | NPT_HttpEntity::GetInputStream
546 +---------------------------------------------------------------------*/
548 NPT_HttpEntity::GetInputStream(NPT_InputStreamReference
& stream
)
550 // reset output params first
553 if (m_InputStream
.IsNull()) return NPT_FAILURE
;
555 stream
= m_InputStream
;
559 /*----------------------------------------------------------------------
560 | NPT_HttpEntity::SetInputStream
561 +---------------------------------------------------------------------*/
563 NPT_HttpEntity::SetInputStream(const NPT_InputStreamReference
& stream
,
564 bool update_content_length
/* = false */)
566 m_InputStream
= stream
;
568 // get the content length from the stream
569 if (update_content_length
&& !stream
.IsNull()) {
570 NPT_LargeSize length
;
571 if (NPT_SUCCEEDED(stream
->GetSize(length
))) {
572 return SetContentLength(length
);
579 /*----------------------------------------------------------------------
580 | NPT_HttpEntity::SetInputStream
581 +---------------------------------------------------------------------*/
583 NPT_HttpEntity::SetInputStream(const void* data
, NPT_Size data_size
)
585 NPT_MemoryStream
* memory_stream
= new NPT_MemoryStream(data
, data_size
);
586 NPT_InputStreamReference
body(memory_stream
);
587 return SetInputStream(body
, true);
590 /*----------------------------------------------------------------------
591 | NPT_HttpEntity::SetInputStream
592 +---------------------------------------------------------------------*/
594 NPT_HttpEntity::SetInputStream(const char* string
)
596 if (string
== NULL
) return NPT_ERROR_INVALID_PARAMETERS
;
597 NPT_MemoryStream
* memory_stream
= new NPT_MemoryStream((const void*)string
,
598 NPT_StringLength(string
));
599 NPT_InputStreamReference
body(memory_stream
);
600 return SetInputStream(body
, true);
603 /*----------------------------------------------------------------------
604 | NPT_HttpEntity::SetInputStream
605 +---------------------------------------------------------------------*/
607 NPT_HttpEntity::SetInputStream(const NPT_String
& string
)
609 NPT_MemoryStream
* memory_stream
= new NPT_MemoryStream((const void*)string
.GetChars(),
611 NPT_InputStreamReference
body(memory_stream
);
612 return SetInputStream(body
, true);
615 /*----------------------------------------------------------------------
616 | NPT_HttpEntity::Load
617 +---------------------------------------------------------------------*/
619 NPT_HttpEntity::Load(NPT_DataBuffer
& buffer
)
621 // check that we have an input stream
622 if (m_InputStream
.IsNull()) return NPT_ERROR_INVALID_STATE
;
624 // load the stream into the buffer
625 if (m_ContentLength
!= (NPT_Size
)m_ContentLength
) return NPT_ERROR_OUT_OF_RANGE
;
626 return m_InputStream
->Load(buffer
, (NPT_Size
)m_ContentLength
);
629 /*----------------------------------------------------------------------
630 | NPT_HttpEntity::SetContentLength
631 +---------------------------------------------------------------------*/
633 NPT_HttpEntity::SetContentLength(NPT_LargeSize length
)
635 m_ContentLength
= length
;
636 m_ContentLengthIsKnown
= true;
640 /*----------------------------------------------------------------------
641 | NPT_HttpEntity::SetContentType
642 +---------------------------------------------------------------------*/
644 NPT_HttpEntity::SetContentType(const char* type
)
646 m_ContentType
= type
;
650 /*----------------------------------------------------------------------
651 | NPT_HttpEntity::SetContentEncoding
652 +---------------------------------------------------------------------*/
654 NPT_HttpEntity::SetContentEncoding(const char* encoding
)
656 m_ContentEncoding
= encoding
;
660 /*----------------------------------------------------------------------
661 | NPT_HttpEntity::SetTransferEncoding
662 +---------------------------------------------------------------------*/
664 NPT_HttpEntity::SetTransferEncoding(const char* encoding
)
666 m_TransferEncoding
= encoding
;
670 /*----------------------------------------------------------------------
671 | NPT_HttpMessage::NPT_HttpMessage
672 +---------------------------------------------------------------------*/
673 NPT_HttpMessage::NPT_HttpMessage(const char* protocol
) :
674 m_Protocol(protocol
),
679 /*----------------------------------------------------------------------
680 | NPT_HttpMessage::NPT_HttpMessage
681 +---------------------------------------------------------------------*/
682 NPT_HttpMessage::~NPT_HttpMessage()
687 /*----------------------------------------------------------------------
688 | NPT_HttpMessage::SetEntity
689 +---------------------------------------------------------------------*/
691 NPT_HttpMessage::SetEntity(NPT_HttpEntity
* entity
)
693 if (entity
!= m_Entity
) {
701 /*----------------------------------------------------------------------
702 | NPT_HttpMessage::ParseHeaders
703 +---------------------------------------------------------------------*/
705 NPT_HttpMessage::ParseHeaders(NPT_BufferedInputStream
& stream
)
707 return m_Headers
.Parse(stream
);
710 /*----------------------------------------------------------------------
711 | NPT_HttpRequest::NPT_HttpRequest
712 +---------------------------------------------------------------------*/
713 NPT_HttpRequest::NPT_HttpRequest(const NPT_HttpUrl
& url
,
715 const char* protocol
) :
716 NPT_HttpMessage(protocol
),
722 /*----------------------------------------------------------------------
723 | NPT_HttpRequest::NPT_HttpRequest
724 +---------------------------------------------------------------------*/
725 NPT_HttpRequest::NPT_HttpRequest(const char* url
,
727 const char* protocol
) :
728 NPT_HttpMessage(protocol
),
734 /*----------------------------------------------------------------------
735 | NPT_HttpRequest::SetUrl
736 +---------------------------------------------------------------------*/
738 NPT_HttpRequest::SetUrl(const char* url
)
744 /*----------------------------------------------------------------------
745 | NPT_HttpRequest::SetUrl
746 +---------------------------------------------------------------------*/
748 NPT_HttpRequest::SetUrl(const NPT_HttpUrl
& url
)
754 /*----------------------------------------------------------------------
755 | NPT_HttpRequest::Parse
756 +---------------------------------------------------------------------*/
758 NPT_HttpRequest::Parse(NPT_BufferedInputStream
& stream
,
759 const NPT_SocketAddress
* endpoint
,
760 NPT_HttpRequest
*& request
)
762 // default return value
765 skip_first_empty_line
:
766 // read the request line
768 NPT_CHECK_FINER(stream
.ReadLine(line
, NPT_HTTP_PROTOCOL_MAX_LINE_LENGTH
));
769 NPT_LOG_FINEST_1("http request: %s", line
.GetChars());
771 // cleanup lines that may contain '\0' as first character, clients such
772 // Spotify desktop app send SSDP M-SEARCH requests followed by an extra
773 // '\0' character which stays in the buffered stream and messes up parsing
775 while (line
.GetLength() > 0 && line
[0] == '\0') {
776 line
= line
.Erase(0, 1);
779 // when using keep-alive connections, clients such as XBox 360
780 // incorrectly send a few empty lines as body for GET requests
781 // so we try to skip them until we find something to parse
782 if (line
.GetLength() == 0) goto skip_first_empty_line
;
784 // check the request line
785 int first_space
= line
.Find(' ');
786 if (first_space
< 0) {
787 NPT_LOG_FINE_1("http request: %s", line
.GetChars());
788 return NPT_ERROR_HTTP_INVALID_REQUEST_LINE
;
790 int second_space
= line
.Find(' ', first_space
+1);
791 if (second_space
< 0) {
792 NPT_LOG_FINE_1("http request: %s", line
.GetChars());
793 return NPT_ERROR_HTTP_INVALID_REQUEST_LINE
;
796 // parse the request line
797 NPT_String method
= line
.SubString(0, first_space
);
798 NPT_String uri
= line
.SubString(first_space
+1, second_space
-first_space
-1);
799 NPT_String protocol
= line
.SubString(second_space
+1);
802 bool proxy_style_request
= false;
803 if (uri
.StartsWith("http://", true)) {
804 // proxy-style request with absolute URI
805 request
= new NPT_HttpRequest(uri
, method
, protocol
);
806 proxy_style_request
= true;
808 // normal absolute path request
809 request
= new NPT_HttpRequest("http:", method
, protocol
);
813 NPT_Result result
= request
->ParseHeaders(stream
);
814 if (NPT_FAILED(result
)) {
821 if (!proxy_style_request
) {
822 request
->m_Url
.SetScheme("http");
823 request
->m_Url
.ParsePathPlus(uri
);
824 request
->m_Url
.SetPort(NPT_HTTP_DEFAULT_PORT
);
826 // check for a Host: header
827 NPT_HttpHeader
* host_header
= request
->GetHeaders().GetHeader(NPT_HTTP_HEADER_HOST
);
829 request
->m_Url
.SetHost(host_header
->GetValue());
831 // host sometimes doesn't contain port
833 request
->m_Url
.SetPort(endpoint
->GetPort());
836 // use the endpoint as the host
838 request
->m_Url
.SetHost(endpoint
->ToString());
841 request
->m_Url
.SetHost("localhost");
849 /*----------------------------------------------------------------------
850 | NPT_HttpRequest::~NPT_HttpRequest
851 +---------------------------------------------------------------------*/
852 NPT_HttpRequest::~NPT_HttpRequest()
856 /*----------------------------------------------------------------------
857 | NPT_HttpRequest::Emit
858 +---------------------------------------------------------------------*/
860 NPT_HttpRequest::Emit(NPT_OutputStream
& stream
, bool use_proxy
) const
862 // write the request line
863 stream
.WriteString(m_Method
);
864 stream
.WriteFully(" ", 1);
866 stream
.WriteString(m_Url
.ToString(false));
868 stream
.WriteString(m_Url
.ToRequestString());
870 stream
.WriteFully(" ", 1);
871 stream
.WriteString(m_Protocol
);
872 stream
.WriteFully(NPT_HTTP_LINE_TERMINATOR
, 2);
875 m_Headers
.Emit(stream
);
877 // finish with an empty line
878 stream
.WriteFully(NPT_HTTP_LINE_TERMINATOR
, 2);
883 /*----------------------------------------------------------------------
884 | NPT_HttpResponse::NPT_HttpResponse
885 +---------------------------------------------------------------------*/
886 NPT_HttpResponse::NPT_HttpResponse(NPT_HttpStatusCode status_code
,
887 const char* reason_phrase
,
888 const char* protocol
) :
889 NPT_HttpMessage(protocol
),
890 m_StatusCode(status_code
),
891 m_ReasonPhrase(reason_phrase
)
895 /*----------------------------------------------------------------------
896 | NPT_HttpResponse::~NPT_HttpResponse
897 +---------------------------------------------------------------------*/
898 NPT_HttpResponse::~NPT_HttpResponse()
902 /*----------------------------------------------------------------------
903 | NPT_HttpResponse::SetStatus
904 +---------------------------------------------------------------------*/
906 NPT_HttpResponse::SetStatus(NPT_HttpStatusCode status_code
,
907 const char* reason_phrase
,
908 const char* protocol
)
910 m_StatusCode
= status_code
;
911 m_ReasonPhrase
= reason_phrase
;
912 if (protocol
) m_Protocol
= protocol
;
916 /*----------------------------------------------------------------------
917 | NPT_HttpResponse::SetProtocol
918 +---------------------------------------------------------------------*/
920 NPT_HttpResponse::SetProtocol(const char* protocol
)
922 m_Protocol
= protocol
;
926 /*----------------------------------------------------------------------
927 | NPT_HttpResponse::Emit
928 +---------------------------------------------------------------------*/
930 NPT_HttpResponse::Emit(NPT_OutputStream
& stream
) const
932 // write the request line
933 stream
.WriteString(m_Protocol
);
934 stream
.WriteFully(" ", 1);
935 stream
.WriteString(NPT_String::FromInteger(m_StatusCode
));
936 stream
.WriteFully(" ", 1);
937 stream
.WriteString(m_ReasonPhrase
);
938 stream
.WriteFully(NPT_HTTP_LINE_TERMINATOR
, 2);
941 m_Headers
.Emit(stream
);
943 // finish with an empty line
944 stream
.WriteFully(NPT_HTTP_LINE_TERMINATOR
, 2);
949 /*----------------------------------------------------------------------
950 | NPT_HttpResponse::Parse
951 +---------------------------------------------------------------------*/
953 NPT_HttpResponse::Parse(NPT_BufferedInputStream
& stream
,
954 NPT_HttpResponse
*& response
)
956 // default return value
959 // read the response line
961 NPT_CHECK_WARNING(stream
.ReadLine(line
, NPT_HTTP_PROTOCOL_MAX_LINE_LENGTH
));
963 NPT_LOG_FINER_1("http response: %s", line
.GetChars());
965 // check the response line
966 // we are lenient here, as we allow the response to deviate slightly from
967 // strict HTTP (for example, ICY servers response with a method equal to
968 // ICY insead of HTTP/1.X)
969 int first_space
= line
.Find(' ');
970 if (first_space
< 1) return NPT_ERROR_HTTP_INVALID_RESPONSE_LINE
;
971 int second_space
= line
.Find(' ', first_space
+1);
972 if (second_space
< 0) {
973 // some servers omit (incorrectly) the space and Reason-Code
974 // but we don't fail them just for that. Just check that the
975 // status code looks ok
976 if (line
.GetLength() != 12) {
977 return NPT_ERROR_HTTP_INVALID_RESPONSE_LINE
;
979 } else if (second_space
-first_space
!= 4) {
980 // the status code is not of length 3
981 return NPT_ERROR_HTTP_INVALID_RESPONSE_LINE
;
984 // parse the response line
985 NPT_String protocol
= line
.SubString(0, first_space
);
986 NPT_String status_code
= line
.SubString(first_space
+1, 3);
987 NPT_String reason_phrase
= line
.SubString(first_space
+1+3+1,
988 line
.GetLength()-(first_space
+1+3+1));
990 // create a response object
991 NPT_UInt32 status_code_int
= 0;
992 status_code
.ToInteger(status_code_int
);
993 response
= new NPT_HttpResponse(status_code_int
, reason_phrase
, protocol
);
996 NPT_Result result
= response
->ParseHeaders(stream
);
997 if (NPT_FAILED(result
)) {
1005 /*----------------------------------------------------------------------
1006 | NPT_HttpEnvProxySelector
1007 +---------------------------------------------------------------------*/
1008 class NPT_HttpEnvProxySelector
: public NPT_HttpProxySelector
,
1009 public NPT_AutomaticCleaner::Singleton
1012 static NPT_HttpEnvProxySelector
* GetInstance();
1014 // NPT_HttpProxySelector methods
1015 NPT_Result
GetProxyForUrl(const NPT_HttpUrl
& url
, NPT_HttpProxyAddress
& proxy
) override
;
1019 static NPT_HttpEnvProxySelector
* Instance
;
1022 static void ParseProxyEnv(const NPT_String
& env
, NPT_HttpProxyAddress
& proxy
);
1025 NPT_HttpProxyAddress m_HttpProxy
;
1026 NPT_HttpProxyAddress m_HttpsProxy
;
1027 NPT_List
<NPT_String
> m_NoProxy
;
1028 NPT_HttpProxyAddress m_AllProxy
;
1030 NPT_HttpEnvProxySelector
* NPT_HttpEnvProxySelector::Instance
= NULL
;
1032 /*----------------------------------------------------------------------
1033 | NPT_HttpEnvProxySelector::GetInstance
1034 +---------------------------------------------------------------------*/
1035 NPT_HttpEnvProxySelector
*
1036 NPT_HttpEnvProxySelector::GetInstance()
1038 if (Instance
) return Instance
;
1040 NPT_SingletonLock::GetInstance().Lock();
1041 if (Instance
== NULL
) {
1042 // create the shared instance
1043 Instance
= new NPT_HttpEnvProxySelector();
1045 // prepare for recycling
1046 NPT_AutomaticCleaner::GetInstance()->Register(Instance
);
1048 // parse the http proxy settings
1049 NPT_String http_proxy
;
1050 NPT_Environment::Get("http_proxy", http_proxy
);
1051 ParseProxyEnv(http_proxy
, Instance
->m_HttpProxy
);
1052 NPT_LOG_FINE_2("http_proxy: %s:%d", Instance
->m_HttpProxy
.GetHostName().GetChars(), Instance
->m_HttpProxy
.GetPort());
1054 // parse the https proxy settings
1055 NPT_String https_proxy
;
1056 if (NPT_FAILED(NPT_Environment::Get("HTTPS_PROXY", https_proxy
))) {
1057 NPT_Environment::Get("https_proxy", https_proxy
);
1059 ParseProxyEnv(https_proxy
, Instance
->m_HttpsProxy
);
1060 NPT_LOG_FINE_2("https_proxy: %s:%d", Instance
->m_HttpsProxy
.GetHostName().GetChars(), Instance
->m_HttpsProxy
.GetPort());
1062 // parse the all-proxy settings
1063 NPT_String all_proxy
;
1064 if (NPT_FAILED(NPT_Environment::Get("ALL_PROXY", all_proxy
))) {
1065 NPT_Environment::Get("all_proxy", all_proxy
);
1067 ParseProxyEnv(all_proxy
, Instance
->m_AllProxy
);
1068 NPT_LOG_FINE_2("all_proxy: %s:%d", Instance
->m_AllProxy
.GetHostName().GetChars(), Instance
->m_AllProxy
.GetPort());
1070 // parse the no-proxy settings
1071 NPT_String no_proxy
;
1072 if (NPT_FAILED(NPT_Environment::Get("NO_PROXY", no_proxy
))) {
1073 NPT_Environment::Get("no_proxy", no_proxy
);
1075 if (no_proxy
.GetLength()) {
1076 Instance
->m_NoProxy
= no_proxy
.Split(",");
1079 NPT_SingletonLock::GetInstance().Unlock();
1084 /*----------------------------------------------------------------------
1085 | NPT_HttpEnvProxySelector::ParseProxyEnv
1086 +---------------------------------------------------------------------*/
1088 NPT_HttpEnvProxySelector::ParseProxyEnv(const NPT_String
& env
,
1089 NPT_HttpProxyAddress
& proxy
)
1091 // ignore empty strings
1092 if (env
.GetLength() == 0) return;
1094 NPT_String proxy_spec
;
1095 if (env
.Find("://") >= 0) {
1098 proxy_spec
= "http://"+env
;
1100 NPT_Url
url(proxy_spec
);
1101 proxy
.SetHostName(url
.GetHost());
1102 proxy
.SetPort(url
.GetPort());
1105 /*----------------------------------------------------------------------
1106 | NPT_HttpEnvProxySelector::GetProxyForUrl
1107 +---------------------------------------------------------------------*/
1109 NPT_HttpEnvProxySelector::GetProxyForUrl(const NPT_HttpUrl
& url
,
1110 NPT_HttpProxyAddress
& proxy
)
1112 NPT_HttpProxyAddress
* protocol_proxy
= NULL
;
1113 switch (url
.GetSchemeId()) {
1114 case NPT_Uri::SCHEME_ID_HTTP
:
1115 protocol_proxy
= &m_HttpProxy
;
1118 case NPT_Uri::SCHEME_ID_HTTPS
:
1119 protocol_proxy
= &m_HttpsProxy
;
1123 return NPT_ERROR_HTTP_NO_PROXY
;
1126 // check for no-proxy first
1127 if (m_NoProxy
.GetItemCount()) {
1128 for (NPT_List
<NPT_String
>::Iterator i
= m_NoProxy
.GetFirstItem();
1132 return NPT_ERROR_HTTP_NO_PROXY
;
1134 if (url
.GetHost().EndsWith(*i
, true)) {
1135 if (url
.GetHost().GetLength() == (*i
).GetLength()) {
1137 return NPT_ERROR_HTTP_NO_PROXY
;
1139 if (url
.GetHost().GetChars()[url
.GetHost().GetLength()-(*i
).GetLength()-1] == '.') {
1141 return NPT_ERROR_HTTP_NO_PROXY
;
1147 // check the protocol proxy
1148 if (protocol_proxy
->GetHostName().GetLength()) {
1149 proxy
= *protocol_proxy
;
1153 // use the default proxy
1156 return proxy
.GetHostName().GetLength()?NPT_SUCCESS
:NPT_ERROR_HTTP_NO_PROXY
;
1159 /*----------------------------------------------------------------------
1160 | NPT_HttpProxySelector::GetDefault
1161 +---------------------------------------------------------------------*/
1162 static bool NPT_HttpProxySelector_ConfigChecked
= false;
1163 static unsigned int NPT_HttpProxySelector_Config
= 0;
1164 const unsigned int NPT_HTTP_PROXY_SELECTOR_CONFIG_NONE
= 0;
1165 const unsigned int NPT_HTTP_PROXY_SELECTOR_CONFIG_ENV
= 1;
1166 const unsigned int NPT_HTTP_PROXY_SELECTOR_CONFIG_SYSTEM
= 2;
1167 NPT_HttpProxySelector
*
1168 NPT_HttpProxySelector::GetDefault()
1170 if (!NPT_HttpProxySelector_ConfigChecked
) {
1172 if (NPT_SUCCEEDED(NPT_Environment::Get("NEPTUNE_NET_CONFIG_PROXY_SELECTOR", config
))) {
1173 if (config
.Compare("noproxy", true) == 0) {
1174 NPT_HttpProxySelector_Config
= NPT_HTTP_PROXY_SELECTOR_CONFIG_NONE
;
1175 } else if (config
.Compare("env", true) == 0) {
1176 NPT_HttpProxySelector_Config
= NPT_HTTP_PROXY_SELECTOR_CONFIG_ENV
;
1177 } else if (config
.Compare("system", true) == 0) {
1178 NPT_HttpProxySelector_Config
= NPT_HTTP_PROXY_SELECTOR_CONFIG_SYSTEM
;
1180 NPT_HttpProxySelector_Config
= NPT_HTTP_PROXY_SELECTOR_CONFIG_NONE
;
1183 NPT_HttpProxySelector_ConfigChecked
= true;
1186 switch (NPT_HttpProxySelector_Config
) {
1187 case NPT_HTTP_PROXY_SELECTOR_CONFIG_NONE
:
1191 case NPT_HTTP_PROXY_SELECTOR_CONFIG_ENV
:
1192 // use the shared instance
1193 return NPT_HttpEnvProxySelector::GetInstance();
1195 case NPT_HTTP_PROXY_SELECTOR_CONFIG_SYSTEM
:
1196 // use the sytem proxy selector
1197 return GetSystemSelector();
1204 /*----------------------------------------------------------------------
1205 | NPT_HttpProxySelector::GetSystemSelector
1206 +---------------------------------------------------------------------*/
1207 #if !defined(NPT_CONFIG_HAVE_SYSTEM_PROXY_SELECTOR)
1208 NPT_HttpProxySelector
*
1209 NPT_HttpProxySelector::GetSystemSelector()
1215 /*----------------------------------------------------------------------
1216 | NPT_HttpStaticProxySelector
1217 +---------------------------------------------------------------------*/
1218 class NPT_HttpStaticProxySelector
: public NPT_HttpProxySelector
1222 NPT_HttpStaticProxySelector(const char* http_propxy_hostname
,
1223 NPT_UInt16 http_proxy_port
,
1224 const char* https_proxy_hostname
,
1225 NPT_UInt16 htts_proxy_port
);
1227 // NPT_HttpProxySelector methods
1228 NPT_Result
GetProxyForUrl(const NPT_HttpUrl
& url
, NPT_HttpProxyAddress
& proxy
) override
;
1232 NPT_HttpProxyAddress m_HttpProxy
;
1233 NPT_HttpProxyAddress m_HttpsProxy
;
1236 /*----------------------------------------------------------------------
1237 | NPT_HttpStaticProxySelector::NPT_HttpStaticProxySelector
1238 +---------------------------------------------------------------------*/
1239 NPT_HttpStaticProxySelector::NPT_HttpStaticProxySelector(const char* http_proxy_hostname
,
1240 NPT_UInt16 http_proxy_port
,
1241 const char* https_proxy_hostname
,
1242 NPT_UInt16 https_proxy_port
) :
1243 m_HttpProxy( http_proxy_hostname
, http_proxy_port
),
1244 m_HttpsProxy(https_proxy_hostname
, https_proxy_port
)
1248 /*----------------------------------------------------------------------
1249 | NPT_HttpStaticProxySelector::GetProxyForUrl
1250 +---------------------------------------------------------------------*/
1252 NPT_HttpStaticProxySelector::GetProxyForUrl(const NPT_HttpUrl
& url
,
1253 NPT_HttpProxyAddress
& proxy
)
1255 switch (url
.GetSchemeId()) {
1256 case NPT_Uri::SCHEME_ID_HTTP
:
1257 proxy
= m_HttpProxy
;
1260 case NPT_Uri::SCHEME_ID_HTTPS
:
1261 proxy
= m_HttpsProxy
;
1265 return NPT_ERROR_HTTP_NO_PROXY
;
1271 /*----------------------------------------------------------------------
1272 | NPT_HttpConnectionManager::NPT_HttpConnectionManager
1273 +---------------------------------------------------------------------*/
1274 NPT_HttpConnectionManager::NPT_HttpConnectionManager() :
1276 m_MaxConnections(NPT_HTTP_CONNECTION_MANAGER_MAX_CONNECTION_POOL_SIZE
),
1277 m_MaxConnectionAge(NPT_HTTP_CONNECTION_MANAGER_MAX_CONNECTION_AGE
)
1281 /*----------------------------------------------------------------------
1282 | NPT_HttpConnectionManager::~NPT_HttpConnectionManager
1283 +---------------------------------------------------------------------*/
1284 NPT_HttpConnectionManager::~NPT_HttpConnectionManager()
1286 // set abort flag and wait for thread to finish
1287 m_Aborted
.SetValue(1);
1290 m_Connections
.Apply(NPT_ObjectDeleter
<Connection
>());
1293 /*----------------------------------------------------------------------
1294 | NPT_HttpConnectionManager::GetInstance
1295 +---------------------------------------------------------------------*/
1296 NPT_HttpConnectionManager
*
1297 NPT_HttpConnectionManager::GetInstance()
1299 if (Instance
) return Instance
;
1301 NPT_SingletonLock::GetInstance().Lock();
1302 if (Instance
== NULL
) {
1303 // create the shared instance
1304 Instance
= new NPT_HttpConnectionManager();
1306 // register to for automatic cleanup
1307 NPT_AutomaticCleaner::GetInstance()->RegisterHttpConnectionManager(Instance
);
1309 // Start shared instance
1312 NPT_SingletonLock::GetInstance().Unlock();
1316 NPT_HttpConnectionManager
* NPT_HttpConnectionManager::Instance
= NULL
;
1318 /*----------------------------------------------------------------------
1319 | NPT_HttpConnectionManager::Run
1320 +---------------------------------------------------------------------*/
1322 NPT_HttpConnectionManager::Run()
1324 // try to cleanup every 5 secs
1325 while (m_Aborted
.WaitUntilEquals(1, 5000) == NPT_ERROR_TIMEOUT
) {
1326 NPT_AutoLock
lock(m_Lock
);
1331 /*----------------------------------------------------------------------
1332 | NPT_HttpConnectionManager::Cleanup
1333 +---------------------------------------------------------------------*/
1335 NPT_HttpConnectionManager::Cleanup()
1338 NPT_System::GetCurrentTimeStamp(now
);
1339 NPT_TimeStamp
delta((float)m_MaxConnectionAge
);
1341 NPT_List
<Connection
*>::Iterator tail
= m_Connections
.GetLastItem();
1343 if (now
< (*tail
)->m_TimeStamp
+ delta
) break;
1344 NPT_LOG_FINE_1("cleaning up connection (%d remain)", m_Connections
.GetItemCount());
1346 m_Connections
.Erase(tail
);
1347 tail
= m_Connections
.GetLastItem();
1352 /*----------------------------------------------------------------------
1353 | NPT_HttpConnectionManager::FindConnection
1354 +---------------------------------------------------------------------*/
1355 NPT_HttpConnectionManager::Connection
*
1356 NPT_HttpConnectionManager::FindConnection(NPT_SocketAddress
& address
)
1358 NPT_AutoLock
lock(m_Lock
);
1361 for (NPT_List
<Connection
*>::Iterator i
= m_Connections
.GetFirstItem();
1364 Connection
* connection
= *i
;
1366 NPT_SocketInfo info
;
1367 if (NPT_FAILED(connection
->GetInfo(info
))) continue;
1369 if (info
.remote_address
== address
) {
1370 m_Connections
.Erase(i
);
1379 /*----------------------------------------------------------------------
1380 | NPT_HttpConnectionManager::Track
1381 +---------------------------------------------------------------------*/
1383 NPT_HttpConnectionManager::Track(NPT_HttpClient
* client
, NPT_HttpClient::Connection
* connection
)
1385 NPT_AutoLock
lock(m_Lock
);
1387 // look if already tracking client connections
1388 ConnectionList
* connections
= NULL
;
1389 if (NPT_SUCCEEDED(m_ClientConnections
.Get(client
, connections
))) {
1390 // return immediately if connection is already associated with client
1391 if (connections
->Find(NPT_ObjectComparator
<NPT_HttpClient::Connection
*>(connection
))) {
1392 NPT_LOG_WARNING("Connection already associated to client.");
1395 connections
->Add(connection
);
1399 // new client connections
1400 ConnectionList new_connections
;
1402 // add connection to new client connection list
1403 new_connections
.Add(connection
);
1405 // track new client connections
1406 m_ClientConnections
.Put(client
, new_connections
);
1410 /*----------------------------------------------------------------------
1411 | NPT_HttpConnectionManager::UntrackConnection
1412 +---------------------------------------------------------------------*/
1414 NPT_HttpConnectionManager::UntrackConnection(NPT_HttpClient::Connection
* connection
)
1416 NPT_AutoLock
lock(m_Lock
);
1419 return NPT_ERROR_INVALID_PARAMETERS
;
1422 // look for connection by enumerating all client connections
1423 NPT_List
<NPT_Map
<NPT_HttpClient
*, ConnectionList
>::Entry
*>::Iterator entry
=
1424 m_ClientConnections
.GetEntries().GetFirstItem();
1426 NPT_HttpClient
*& client
= (NPT_HttpClient
*&)(*entry
)->GetKey();
1427 ConnectionList
& connections
= (ConnectionList
&)(*entry
)->GetValue();
1429 // look for connection in client connection list
1430 NPT_List
<NPT_HttpClient::Connection
*>::Iterator i
=
1431 connections
.Find(NPT_ObjectComparator
<NPT_HttpClient::Connection
*>(connection
));
1434 connections
.Erase(i
);
1436 // untrack client if no more active connections for it
1437 if (connections
.GetItemCount() == 0) {
1438 m_ClientConnections
.Erase(client
);
1446 return NPT_ERROR_NO_SUCH_ITEM
;
1449 /*----------------------------------------------------------------------
1450 | NPT_HttpConnectionManager::Untrack
1451 +---------------------------------------------------------------------*/
1453 NPT_HttpConnectionManager::Untrack(NPT_HttpClient::Connection
* connection
)
1455 // check first if ConnectionCanceller Instance has not been released already
1456 // with static finalizers
1457 if (Instance
== NULL
) return NPT_FAILURE
;
1459 return GetInstance()->UntrackConnection(connection
);
1462 /*----------------------------------------------------------------------
1463 | NPT_HttpConnectionManager::Recycle
1464 +---------------------------------------------------------------------*/
1466 NPT_HttpConnectionManager::Recycle(NPT_HttpConnectionManager::Connection
* connection
)
1468 // Untrack connection
1469 UntrackConnection(connection
);
1472 NPT_AutoLock
lock(m_Lock
);
1475 // remove older connections to make room
1476 while (m_Connections
.GetItemCount() >= m_MaxConnections
) {
1477 NPT_List
<Connection
*>::Iterator head
= m_Connections
.GetFirstItem();
1480 m_Connections
.Erase(head
);
1481 NPT_LOG_FINER("removing connection from pool to make some room");
1486 // label this connection with the current timestamp and flag
1487 NPT_System::GetCurrentTimeStamp(connection
->m_TimeStamp
);
1488 connection
->m_IsRecycled
= true;
1490 // add the connection to the pool
1491 m_Connections
.Add(connection
);
1498 /*----------------------------------------------------------------------
1499 | NPT_HttpConnectionManager::AbortConnections
1500 +---------------------------------------------------------------------*/
1502 NPT_HttpConnectionManager::AbortConnections(NPT_HttpClient
* client
)
1504 NPT_AutoLock
lock(m_Lock
);
1506 ConnectionList
* connections
= NULL
;
1507 if (NPT_SUCCEEDED(m_ClientConnections
.Get(client
, connections
))) {
1508 for (NPT_List
<NPT_HttpClient::Connection
*>::Iterator i
= connections
->GetFirstItem();
1517 /*----------------------------------------------------------------------
1518 | NPT_HttpConnectionManager::Connection::Connection
1519 +---------------------------------------------------------------------*/
1520 NPT_HttpConnectionManager::Connection::Connection(NPT_HttpConnectionManager
& manager
,
1521 NPT_SocketReference
& socket
,
1522 NPT_InputStreamReference input_stream
,
1523 NPT_OutputStreamReference output_stream
) :
1525 m_IsRecycled(false),
1527 m_InputStream(input_stream
),
1528 m_OutputStream(output_stream
)
1532 /*----------------------------------------------------------------------
1533 | NPT_HttpConnectionManager::Connection::~Connection
1534 +---------------------------------------------------------------------*/
1535 NPT_HttpConnectionManager::Connection::~Connection()
1537 NPT_HttpConnectionManager::Untrack(this);
1540 /*----------------------------------------------------------------------
1541 | NPT_HttpConnectionManager::Connection::Recycle
1542 +---------------------------------------------------------------------*/
1544 NPT_HttpConnectionManager::Connection::Recycle()
1546 return m_Manager
.Recycle(this);
1549 /*----------------------------------------------------------------------
1550 | NPT_HttpClient::NPT_HttpClient
1551 +---------------------------------------------------------------------*/
1552 NPT_HttpClient::NPT_HttpClient(Connector
* connector
, bool transfer_ownership
) :
1553 m_ProxySelector(NPT_HttpProxySelector::GetDefault()),
1554 m_ProxySelectorIsOwned(false),
1555 m_Connector(connector
),
1556 m_ConnectorIsOwned(transfer_ownership
),
1559 if (connector
== NULL
) {
1560 m_Connector
= new NPT_HttpTlsConnector();
1561 m_ConnectorIsOwned
= true;
1565 /*----------------------------------------------------------------------
1566 | NPT_HttpClient::~NPT_HttpClient
1567 +---------------------------------------------------------------------*/
1568 NPT_HttpClient::~NPT_HttpClient()
1570 if (m_ProxySelectorIsOwned
) {
1571 delete m_ProxySelector
;
1573 if (m_ConnectorIsOwned
) {
1578 /*----------------------------------------------------------------------
1579 | NPT_HttpClient::SetConfig
1580 +---------------------------------------------------------------------*/
1582 NPT_HttpClient::SetConfig(const Config
& config
)
1589 /*----------------------------------------------------------------------
1590 | NPT_HttpClient::SetProxy
1591 +---------------------------------------------------------------------*/
1593 NPT_HttpClient::SetProxy(const char* http_proxy_hostname
,
1594 NPT_UInt16 http_proxy_port
,
1595 const char* https_proxy_hostname
,
1596 NPT_UInt16 https_proxy_port
)
1598 if (m_ProxySelectorIsOwned
) {
1599 delete m_ProxySelector
;
1600 m_ProxySelector
= NULL
;
1601 m_ProxySelectorIsOwned
= false;
1604 // use a static proxy to hold on to the settings
1605 m_ProxySelector
= new NPT_HttpStaticProxySelector(http_proxy_hostname
,
1607 https_proxy_hostname
,
1609 m_ProxySelectorIsOwned
= true;
1614 /*----------------------------------------------------------------------
1615 | NPT_HttpClient::SetProxySelector
1616 +---------------------------------------------------------------------*/
1618 NPT_HttpClient::SetProxySelector(NPT_HttpProxySelector
* selector
)
1620 if (m_ProxySelectorIsOwned
&& m_ProxySelector
!= selector
) {
1621 delete m_ProxySelector
;
1623 m_ProxySelector
= selector
;
1624 m_ProxySelectorIsOwned
= false;
1629 /*----------------------------------------------------------------------
1630 | NPT_HttpClient::SetConnector
1631 +---------------------------------------------------------------------*/
1633 NPT_HttpClient::SetConnector(Connector
* connector
)
1635 if (m_ConnectorIsOwned
&& m_Connector
!= connector
) {
1638 m_Connector
= connector
;
1639 m_ConnectorIsOwned
= false;
1644 /*----------------------------------------------------------------------
1645 | NPT_HttpClient::SetTimeouts
1646 +---------------------------------------------------------------------*/
1648 NPT_HttpClient::SetTimeouts(NPT_Timeout connection_timeout
,
1649 NPT_Timeout io_timeout
,
1650 NPT_Timeout name_resolver_timeout
)
1652 m_Config
.m_ConnectionTimeout
= connection_timeout
;
1653 m_Config
.m_IoTimeout
= io_timeout
;
1654 m_Config
.m_NameResolverTimeout
= name_resolver_timeout
;
1659 /*----------------------------------------------------------------------
1660 | NPT_HttpClient::SetUserAgent
1661 +---------------------------------------------------------------------*/
1663 NPT_HttpClient::SetUserAgent(const char* user_agent
)
1665 m_Config
.m_UserAgent
= user_agent
;
1669 /*----------------------------------------------------------------------
1670 | NPT_HttpClient::TrackConnection
1671 +---------------------------------------------------------------------*/
1673 NPT_HttpClient::TrackConnection(Connection
* connection
)
1675 NPT_AutoLock
lock(m_AbortLock
);
1676 if (m_Aborted
) return NPT_ERROR_CANCELLED
;
1677 return NPT_HttpConnectionManager::GetInstance()->Track(this, connection
);
1680 /*----------------------------------------------------------------------
1681 | NPT_HttpClient::SendRequestOnce
1682 +---------------------------------------------------------------------*/
1684 NPT_HttpClient::SendRequestOnce(NPT_HttpRequest
& request
,
1685 NPT_HttpResponse
*& response
,
1686 NPT_HttpRequestContext
* context
/* = NULL */)
1688 // setup default values
1689 NPT_Result result
= NPT_SUCCESS
;
1692 NPT_LOG_FINE_1("requesting URL %s", request
.GetUrl().ToString().GetChars());
1694 // get the address and port to which we need to connect
1695 NPT_HttpProxyAddress proxy
;
1696 bool use_proxy
= false;
1697 if (m_ProxySelector
) {
1698 // we have a proxy selector, ask it to select a proxy for this URL
1699 result
= m_ProxySelector
->GetProxyForUrl(request
.GetUrl(), proxy
);
1700 if (NPT_FAILED(result
) && result
!= NPT_ERROR_HTTP_NO_PROXY
) {
1701 NPT_LOG_WARNING_1("proxy selector failure (%d)", result
);
1704 use_proxy
= !proxy
.GetHostName().IsEmpty();
1707 // connect to the server or proxy
1708 Connection
* connection
= NULL
;
1709 bool http_1_1
= (request
.GetProtocol() == NPT_HTTP_PROTOCOL_1_1
);
1710 NPT_Reference
<Connection
> cref
;
1712 // send the request to the server (in a loop, since we may need to reconnect with 1.1)
1713 bool reconnect
= false;
1714 unsigned int watchdog
= NPT_HTTP_MAX_RECONNECTS
;
1718 NPT_LOG_FINE_3("calling connector (proxy:%s) (http 1.1:%s) (url:%s)",
1719 use_proxy
?"yes":"no", http_1_1
?"yes":"no", request
.GetUrl().ToStringWithDefaultPort(0).GetChars());
1720 NPT_CHECK_WARNING(m_Connector
->Connect(request
.GetUrl(),
1722 use_proxy
?&proxy
:NULL
,
1725 NPT_LOG_FINE_1("got connection (reused: %s)", connection
->IsRecycled()?"true":"false");
1727 NPT_InputStreamReference input_stream
= connection
->GetInputStream();
1728 NPT_OutputStreamReference output_stream
= connection
->GetOutputStream();
1731 reconnect
= connection
->IsRecycled();
1733 // update context if any
1735 NPT_SocketInfo info
;
1736 cref
->GetInfo(info
);
1737 context
->SetLocalAddress(info
.local_address
);
1738 context
->SetRemoteAddress(info
.remote_address
);
1741 NPT_HttpEntity
* entity
= request
.GetEntity();
1742 NPT_InputStreamReference body_stream
;
1744 if (reconnect
&& entity
&& NPT_SUCCEEDED(entity
->GetInputStream(body_stream
)) && NPT_FAILED(body_stream
->Seek(0))) {
1745 // if body is not seekable, we can't afford to reuse a connection
1746 // that could fail, so we reconnect a new one instead
1747 NPT_LOG_FINE("rewinding body stream would fail ... create new connection");
1751 // decide if this connection should persist
1752 NPT_HttpHeaders
& headers
= request
.GetHeaders();
1753 bool should_persist
= http_1_1
;
1754 if (!connection
->SupportsPersistence()) {
1755 should_persist
= false;
1757 if (should_persist
) {
1758 const NPT_String
* connection_header
= headers
.GetHeaderValue(NPT_HTTP_HEADER_CONNECTION
);
1759 if (connection_header
&& (*connection_header
== "close")) {
1760 should_persist
= false;
1764 if (m_Config
.m_UserAgent
.GetLength()) {
1765 headers
.SetHeader(NPT_HTTP_HEADER_USER_AGENT
, m_Config
.m_UserAgent
, false); // set but don't replace
1768 result
= WriteRequest(*output_stream
.AsPointer(), request
, should_persist
, use_proxy
);
1769 if (NPT_FAILED(result
)) {
1770 NPT_LOG_FINE_1("failed to write request headers (%d)", result
);
1771 if (reconnect
&& !m_Aborted
) {
1772 if (!body_stream
.IsNull()) {
1773 // go back to the start of the body so that we can resend
1774 NPT_LOG_FINE("rewinding body stream in order to resend");
1775 result
= body_stream
->Seek(0);
1776 if (NPT_FAILED(result
)) {
1777 NPT_CHECK_FINE(NPT_ERROR_HTTP_CANNOT_RESEND_BODY
);
1786 result
= ReadResponse(input_stream
,
1788 request
.GetMethod() != NPT_HTTP_METHOD_HEAD
,
1791 if (NPT_FAILED(result
)) {
1792 NPT_LOG_FINE_1("failed to parse the response (%d)", result
);
1793 if (reconnect
&& !m_Aborted
/*&&
1794 (result == NPT_ERROR_EOS ||
1795 result == NPT_ERROR_CONNECTION_ABORTED ||
1796 result == NPT_ERROR_CONNECTION_RESET ||
1797 result == NPT_ERROR_READ_FAILED) GBG: don't look for specific error codes */) {
1798 NPT_LOG_FINE("error is not fatal, retrying");
1799 if (!body_stream
.IsNull()) {
1800 // go back to the start of the body so that we can resend
1801 NPT_LOG_FINE("rewinding body stream in order to resend");
1802 result
= body_stream
->Seek(0);
1803 if (NPT_FAILED(result
)) {
1804 NPT_CHECK_FINE(NPT_ERROR_HTTP_CANNOT_RESEND_BODY
);
1814 } while (reconnect
&& --watchdog
&& !m_Aborted
);
1816 // check that we have a valid connection
1817 if (NPT_FAILED(result
) && !m_Aborted
) {
1818 NPT_LOG_FINE("failed after max reconnection attempts");
1819 return NPT_ERROR_HTTP_TOO_MANY_RECONNECTS
;
1825 /*----------------------------------------------------------------------
1826 | NPT_HttpClient::WriteRequest
1827 +---------------------------------------------------------------------*/
1829 NPT_HttpClient::WriteRequest(NPT_OutputStream
& output_stream
,
1830 NPT_HttpRequest
& request
,
1831 bool should_persist
,
1832 bool use_proxy
/* = false */)
1834 NPT_Result result
= NPT_SUCCESS
;
1836 // add any headers that may be missing
1837 NPT_HttpHeaders
& headers
= request
.GetHeaders();
1839 if (!should_persist
) {
1840 headers
.SetHeader(NPT_HTTP_HEADER_CONNECTION
, "close", false); // set but don't replace
1843 NPT_String host
= request
.GetUrl().GetHost();
1844 NPT_UInt16 default_port
= 0;
1845 switch (request
.GetUrl().GetSchemeId()) {
1846 case NPT_Uri::SCHEME_ID_HTTP
: default_port
= NPT_HTTP_DEFAULT_PORT
; break;
1847 case NPT_Uri::SCHEME_ID_HTTPS
: default_port
= NPT_HTTPS_DEFAULT_PORT
; break;
1850 if (request
.GetUrl().GetPort() != default_port
) {
1852 host
+= NPT_String::FromInteger(request
.GetUrl().GetPort());
1854 headers
.SetHeader(NPT_HTTP_HEADER_HOST
, host
, false); // set but don't replace
1856 // get the request entity to set additional headers
1857 NPT_InputStreamReference body_stream
;
1858 NPT_HttpEntity
* entity
= request
.GetEntity();
1859 if (entity
&& NPT_SUCCEEDED(entity
->GetInputStream(body_stream
))) {
1860 // set the content length if known
1861 if (entity
->ContentLengthIsKnown()) {
1862 headers
.SetHeader(NPT_HTTP_HEADER_CONTENT_LENGTH
,
1863 NPT_String::FromInteger(entity
->GetContentLength()));
1867 NPT_String content_type
= entity
->GetContentType();
1868 if (!content_type
.IsEmpty()) {
1869 headers
.SetHeader(NPT_HTTP_HEADER_CONTENT_TYPE
, content_type
);
1873 NPT_String content_encoding
= entity
->GetContentEncoding();
1874 if (!content_encoding
.IsEmpty()) {
1875 headers
.SetHeader(NPT_HTTP_HEADER_CONTENT_ENCODING
, content_encoding
);
1878 // transfer encoding
1879 const NPT_String
& transfer_encoding
= entity
->GetTransferEncoding();
1880 if (!transfer_encoding
.IsEmpty()) {
1881 headers
.SetHeader(NPT_HTTP_HEADER_TRANSFER_ENCODING
, transfer_encoding
);
1885 // create a memory stream to buffer the headers
1886 NPT_MemoryStream header_stream
;
1888 // emit the request headers into the header buffer
1889 request
.Emit(header_stream
, use_proxy
&& request
.GetUrl().GetSchemeId()==NPT_Url::SCHEME_ID_HTTP
);
1892 NPT_CHECK_WARNING(output_stream
.WriteFully(header_stream
.GetData(), header_stream
.GetDataSize()));
1894 // send request body
1895 if (entity
&& !body_stream
.IsNull()) {
1896 // check for chunked transfer encoding
1897 NPT_OutputStream
* dest
= &output_stream
;
1898 if (entity
->GetTransferEncoding() == NPT_HTTP_TRANSFER_ENCODING_CHUNKED
) {
1899 dest
= new NPT_HttpChunkedOutputStream(output_stream
);
1902 NPT_LOG_FINE_1("sending body stream, %lld bytes", entity
->GetContentLength()); //FIXME: Would be 0 for chunked encoding
1903 NPT_LargeSize bytes_written
= 0;
1905 // content length = 0 means copy until input returns EOS
1906 result
= NPT_StreamToStreamCopy(*body_stream
.AsPointer(), *dest
, 0, entity
->GetContentLength(), &bytes_written
);
1907 if (NPT_FAILED(result
)) {
1908 NPT_LOG_FINE_3("body stream only partially sent, %lld bytes (%d:%s)",
1911 NPT_ResultText(result
));
1914 // flush to write out any buffered data left in chunked output if used
1917 // cleanup (this will send zero size chunk followed by CRLF)
1918 if (dest
!= &output_stream
) delete dest
;
1921 // flush the output stream so that everything is sent to the server
1922 output_stream
.Flush();
1927 /*----------------------------------------------------------------------
1928 | NPT_HttpClient::ReadResponse
1929 +---------------------------------------------------------------------*/
1931 NPT_HttpClient::ReadResponse(NPT_InputStreamReference
& input_stream
,
1932 bool should_persist
,
1934 NPT_HttpResponse
*& response
,
1935 NPT_Reference
<Connection
>* cref
/* = NULL */)
1939 // setup default values
1942 // create a buffered stream for this socket stream
1943 NPT_BufferedInputStreamReference
buffered_input_stream(new NPT_BufferedInputStream(input_stream
));
1945 // parse the response
1946 for (unsigned int watchcat
= 0; watchcat
< NPT_HTTP_MAX_100_RESPONSES
; watchcat
++) {
1947 // parse the response
1948 result
= NPT_HttpResponse::Parse(*buffered_input_stream
, response
);
1949 NPT_CHECK_FINE(result
);
1951 if (response
->GetStatusCode() >= 100 && response
->GetStatusCode() < 200) {
1952 NPT_LOG_FINE_1("got %d response, continuing", response
->GetStatusCode());
1957 NPT_LOG_FINER_2("got response, code=%d, msg=%s",
1958 response
->GetStatusCode(),
1959 response
->GetReasonPhrase().GetChars());
1963 // check that we have a valid response
1964 if (response
== NULL
) {
1965 NPT_LOG_FINE("failed after max continuation attempts");
1966 return NPT_ERROR_HTTP_TOO_MANY_RECONNECTS
;
1969 // unbuffer the stream
1970 buffered_input_stream
->SetBufferSize(0);
1972 // decide if we should still try to reuse this connection later on
1973 if (should_persist
) {
1974 const NPT_String
* connection_header
= response
->GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_CONNECTION
);
1975 if (response
->GetProtocol() == NPT_HTTP_PROTOCOL_1_1
) {
1976 if (connection_header
&& (*connection_header
== "close")) {
1977 should_persist
= false;
1980 if (!connection_header
|| (*connection_header
!= "keep-alive")) {
1981 should_persist
= false;
1986 // create an entity if one is expected in the response
1987 if (expect_entity
) {
1988 NPT_HttpEntity
* response_entity
= new NPT_HttpEntity(response
->GetHeaders());
1990 // check if the content length is known
1991 bool have_content_length
= (response
->GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_CONTENT_LENGTH
) != NULL
);
1993 // check for chunked Transfer-Encoding
1994 bool chunked
= false;
1995 if (response_entity
->GetTransferEncoding() == NPT_HTTP_TRANSFER_ENCODING_CHUNKED
) {
1997 response_entity
->SetTransferEncoding(NULL
);
2000 // prepare to transfer ownership of the connection if needed
2001 Connection
* connection
= NULL
;
2003 connection
= cref
->AsPointer();
2004 cref
->Detach(); // release the internal ref
2005 // don't delete connection now so we can abort while readin response body,
2006 // just pass ownership to NPT_HttpEntityBodyInputStream so it can recycle it
2007 // when done if connection should persist
2010 // create the body stream wrapper
2011 NPT_InputStream
* response_body_stream
=
2012 new NPT_HttpEntityBodyInputStream(buffered_input_stream
,
2013 response_entity
->GetContentLength(),
2014 have_content_length
,
2018 response_entity
->SetInputStream(NPT_InputStreamReference(response_body_stream
));
2019 response
->SetEntity(response_entity
);
2021 if (should_persist
&& cref
) {
2022 Connection
* connection
= cref
->AsPointer();
2023 cref
->Detach(); // release the internal ref
2024 connection
->Recycle();
2031 /*----------------------------------------------------------------------
2032 | NPT_HttpClient::SendRequest
2033 +---------------------------------------------------------------------*/
2035 NPT_HttpClient::SendRequest(NPT_HttpRequest
& request
,
2036 NPT_HttpResponse
*& response
,
2037 NPT_HttpRequestContext
* context
/* = NULL */)
2039 NPT_Cardinal watchdog
= m_Config
.m_MaxRedirects
+1;
2043 // reset aborted flag
2049 // check that for GET requests there is no entity
2050 if (request
.GetEntity() != NULL
&&
2051 request
.GetMethod() == NPT_HTTP_METHOD_GET
) {
2052 return NPT_ERROR_HTTP_INVALID_REQUEST
;
2057 result
= SendRequestOnce(request
, response
, context
);
2058 if (NPT_FAILED(result
)) break;
2059 if (response
&& m_Config
.m_MaxRedirects
&&
2060 (request
.GetMethod() == NPT_HTTP_METHOD_GET
||
2061 request
.GetMethod() == NPT_HTTP_METHOD_HEAD
) &&
2062 (response
->GetStatusCode() == 301 ||
2063 response
->GetStatusCode() == 302 ||
2064 response
->GetStatusCode() == 303 ||
2065 response
->GetStatusCode() == 307)) {
2067 const NPT_String
* location
= response
->GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_LOCATION
);
2069 // check for location fields that are not absolute URLs
2070 // (this is not allowed by the standard, but many web servers do it
2071 if (location
->StartsWith("/") ||
2072 (!location
->StartsWith("http://", true) &&
2073 !location
->StartsWith("https://", true))) {
2074 NPT_LOG_FINE_1("Location: header (%s) is not an absolute URL, using it as a relative URL", location
->GetChars());
2075 if (location
->StartsWith("/")) {
2076 NPT_LOG_FINE_1("redirecting to absolute path %s", location
->GetChars());
2077 request
.GetUrl().ParsePathPlus(*location
);
2079 NPT_String redirect_path
= request
.GetUrl().GetPath();
2080 int slash_pos
= redirect_path
.ReverseFind('/');
2081 if (slash_pos
>= 0) {
2082 redirect_path
.SetLength(slash_pos
+1);
2084 redirect_path
= "/";
2086 redirect_path
+= *location
;
2087 NPT_LOG_FINE_1("redirecting to absolute path %s", redirect_path
.GetChars());
2088 request
.GetUrl().ParsePathPlus(redirect_path
);
2091 // replace the request url
2092 NPT_LOG_FINE_1("redirecting to %s", location
->GetChars());
2093 request
.SetUrl(*location
);
2094 // remove host header so it is replaced based on new url
2095 request
.GetHeaders().RemoveHeader(NPT_HTTP_HEADER_HOST
);
2102 } while (keep_going
&& --watchdog
&& !m_Aborted
);
2104 // check if we were bitten by the watchdog
2105 if (watchdog
== 0) {
2106 NPT_LOG_WARNING("too many HTTP redirects");
2107 return NPT_ERROR_HTTP_TOO_MANY_REDIRECTS
;
2113 /*----------------------------------------------------------------------
2114 | NPT_HttpClient::Abort
2115 +---------------------------------------------------------------------*/
2117 NPT_HttpClient::Abort()
2119 NPT_AutoLock
lock(m_AbortLock
);
2122 NPT_HttpConnectionManager::GetInstance()->AbortConnections(this);
2126 /*----------------------------------------------------------------------
2127 | NPT_HttpRequestContext::NPT_HttpRequestContext
2128 +---------------------------------------------------------------------*/
2129 NPT_HttpRequestContext::NPT_HttpRequestContext(const NPT_SocketAddress
* local_address
,
2130 const NPT_SocketAddress
* remote_address
)
2132 if (local_address
) m_LocalAddress
= *local_address
;
2133 if (remote_address
) m_RemoteAddress
= *remote_address
;
2136 /*----------------------------------------------------------------------
2137 | NPT_HttpServer::NPT_HttpServer
2138 +---------------------------------------------------------------------*/
2139 NPT_HttpServer::NPT_HttpServer(NPT_UInt16 listen_port
, bool cancellable
) :
2140 m_Socket(cancellable
?NPT_SOCKET_FLAG_CANCELLABLE
:0),
2142 m_ServerHeader("Neptune/" NPT_NEPTUNE_VERSION_STRING
),
2145 m_Config
.m_ListenAddress
= NPT_IpAddress::Any
;
2146 m_Config
.m_ListenPort
= listen_port
;
2147 m_Config
.m_IoTimeout
= NPT_HTTP_SERVER_DEFAULT_IO_TIMEOUT
;
2148 m_Config
.m_ConnectionTimeout
= NPT_HTTP_SERVER_DEFAULT_CONNECTION_TIMEOUT
;
2149 m_Config
.m_ReuseAddress
= true;
2152 /*----------------------------------------------------------------------
2153 | NPT_HttpServer::NPT_HttpServer
2154 +---------------------------------------------------------------------*/
2155 NPT_HttpServer::NPT_HttpServer(NPT_IpAddress listen_address
,
2156 NPT_UInt16 listen_port
,
2158 m_Socket(cancellable
?NPT_SOCKET_FLAG_CANCELLABLE
:0),
2160 m_ServerHeader("Neptune/" NPT_NEPTUNE_VERSION_STRING
),
2163 m_Config
.m_ListenAddress
= listen_address
;
2164 m_Config
.m_ListenPort
= listen_port
;
2165 m_Config
.m_IoTimeout
= NPT_HTTP_SERVER_DEFAULT_IO_TIMEOUT
;
2166 m_Config
.m_ConnectionTimeout
= NPT_HTTP_SERVER_DEFAULT_CONNECTION_TIMEOUT
;
2167 m_Config
.m_ReuseAddress
= true;
2170 /*----------------------------------------------------------------------
2171 | NPT_HttpServer::~NPT_HttpServer
2172 +---------------------------------------------------------------------*/
2173 NPT_HttpServer::~NPT_HttpServer()
2175 m_RequestHandlers
.Apply(NPT_ObjectDeleter
<HandlerConfig
>());
2178 /*----------------------------------------------------------------------
2179 | NPT_HttpServer::Bind
2180 +---------------------------------------------------------------------*/
2182 NPT_HttpServer::Bind()
2184 // check if we're already bound
2185 if (m_BoundPort
!= 0) return NPT_SUCCESS
;
2188 NPT_Result result
= m_Socket
.Bind(
2189 NPT_SocketAddress(m_Config
.m_ListenAddress
, m_Config
.m_ListenPort
),
2190 m_Config
.m_ReuseAddress
);
2191 if (NPT_FAILED(result
)) return result
;
2193 // update the bound port info
2194 NPT_SocketInfo info
;
2195 m_Socket
.GetInfo(info
);
2196 m_BoundPort
= info
.local_address
.GetPort();
2201 /*----------------------------------------------------------------------
2202 | NPT_HttpServer::SetConfig
2203 +---------------------------------------------------------------------*/
2205 NPT_HttpServer::SetConfig(const Config
& config
)
2209 // check that we can bind to this listen port
2213 /*----------------------------------------------------------------------
2214 | NPT_HttpServer::SetListenPort
2215 +---------------------------------------------------------------------*/
2217 NPT_HttpServer::SetListenPort(NPT_UInt16 port
, bool reuse_address
)
2219 m_Config
.m_ListenPort
= port
;
2220 m_Config
.m_ReuseAddress
= reuse_address
;
2224 /*----------------------------------------------------------------------
2225 | NPT_HttpServer::SetTimeouts
2226 +---------------------------------------------------------------------*/
2228 NPT_HttpServer::SetTimeouts(NPT_Timeout connection_timeout
,
2229 NPT_Timeout io_timeout
)
2231 m_Config
.m_ConnectionTimeout
= connection_timeout
;
2232 m_Config
.m_IoTimeout
= io_timeout
;
2237 /*----------------------------------------------------------------------
2238 | NPT_HttpServer::SetServerHeader
2239 +---------------------------------------------------------------------*/
2241 NPT_HttpServer::SetServerHeader(const char* server_header
)
2243 m_ServerHeader
= server_header
;
2247 /*----------------------------------------------------------------------
2248 | NPT_HttpServer::Abort
2249 +---------------------------------------------------------------------*/
2251 NPT_HttpServer::Abort()
2257 /*----------------------------------------------------------------------
2258 | NPT_HttpServer::WaitForNewClient
2259 +---------------------------------------------------------------------*/
2261 NPT_HttpServer::WaitForNewClient(NPT_InputStreamReference
& input
,
2262 NPT_OutputStreamReference
& output
,
2263 NPT_HttpRequestContext
* context
,
2264 NPT_Flags socket_flags
)
2266 // ensure that we're bound
2267 NPT_CHECK_FINE(Bind());
2269 // wait for a connection
2271 NPT_LOG_FINE_2("waiting for new connection on %s:%d...",
2272 (const char*)m_Config
.m_ListenAddress
.ToString(),
2274 NPT_Result result
= m_Socket
.WaitForNewClient(client
, m_Config
.m_ConnectionTimeout
, socket_flags
);
2275 if (result
!= NPT_ERROR_TIMEOUT
) {
2276 NPT_CHECK_WARNING(result
);
2278 NPT_CHECK_FINE(result
);
2280 if (client
== NULL
) return NPT_ERROR_INTERNAL
;
2282 // get the client info
2284 NPT_SocketInfo client_info
;
2285 client
->GetInfo(client_info
);
2287 context
->SetLocalAddress(client_info
.local_address
);
2288 context
->SetRemoteAddress(client_info
.remote_address
);
2290 NPT_LOG_FINE_2("client connected (%s <- %s)",
2291 client_info
.local_address
.ToString().GetChars(),
2292 client_info
.remote_address
.ToString().GetChars());
2295 // configure the socket
2296 client
->SetReadTimeout(m_Config
.m_IoTimeout
);
2297 client
->SetWriteTimeout(m_Config
.m_IoTimeout
);
2300 client
->GetInputStream(input
);
2301 client
->GetOutputStream(output
);
2303 // we don't need the socket anymore
2309 /*----------------------------------------------------------------------
2310 | NPT_HttpServer::Loop
2311 +---------------------------------------------------------------------*/
2313 NPT_HttpServer::Loop(bool cancellable_sockets
)
2315 NPT_InputStreamReference input
;
2316 NPT_OutputStreamReference output
;
2317 NPT_HttpRequestContext context
;
2321 // wait for a client to connect
2322 NPT_Flags flags
= cancellable_sockets
?NPT_SOCKET_FLAG_CANCELLABLE
:0;
2323 result
= WaitForNewClient(input
, output
, &context
, flags
);
2324 NPT_LOG_FINE_2("WaitForNewClient returned %d (%s)",
2326 NPT_ResultText(result
));
2328 if (result
== NPT_ERROR_TIMEOUT
) continue;
2330 // respond to the client
2331 if (NPT_SUCCEEDED(result
)) {
2333 result
= RespondToClient(input
, output
, context
);
2334 NPT_LOG_FINE_2("ResponToClient returned %d (%s)",
2336 NPT_ResultText(result
));
2338 NPT_LOG_FINE_2("WaitForNewClient returned %d (%s)",
2340 NPT_ResultText(result
));
2341 // if there was an error, wait a short time to avoid spinning
2342 if (result
!= NPT_ERROR_TERMINATED
) {
2343 NPT_LOG_FINE("sleeping before restarting the loop");
2344 NPT_System::Sleep(1.0);
2348 // release the stream references so that the socket can be closed
2351 } while (m_Run
&& result
!= NPT_ERROR_TERMINATED
);
2356 /*----------------------------------------------------------------------
2357 | NPT_HttpServer::HandlerConfig::HandlerConfig
2358 +---------------------------------------------------------------------*/
2359 NPT_HttpServer::HandlerConfig::HandlerConfig(NPT_HttpRequestHandler
* handler
,
2361 bool include_children
,
2362 bool transfer_ownership
) :
2365 m_IncludeChildren(include_children
),
2366 m_HandlerIsOwned(transfer_ownership
)
2370 /*----------------------------------------------------------------------
2371 | NPT_HttpServer::HandlerConfig::~HandlerConfig
2372 +---------------------------------------------------------------------*/
2373 NPT_HttpServer::HandlerConfig::~HandlerConfig()
2375 if (m_HandlerIsOwned
) delete m_Handler
;
2378 /*----------------------------------------------------------------------
2379 | NPT_HttpServer::AddRequestHandler
2380 +---------------------------------------------------------------------*/
2382 NPT_HttpServer::AddRequestHandler(NPT_HttpRequestHandler
* handler
,
2384 bool include_children
,
2385 bool transfer_ownership
)
2387 return m_RequestHandlers
.Add(new HandlerConfig(handler
, path
, include_children
, transfer_ownership
));
2390 /*----------------------------------------------------------------------
2391 | NPT_HttpServer::FindRequestHandler
2392 +---------------------------------------------------------------------*/
2393 NPT_HttpRequestHandler
*
2394 NPT_HttpServer::FindRequestHandler(NPT_HttpRequest
& request
)
2396 NPT_String path
= NPT_Uri::PercentDecode(request
.GetUrl().GetPath());
2397 for (NPT_List
<HandlerConfig
*>::Iterator it
= m_RequestHandlers
.GetFirstItem();
2400 HandlerConfig
* config
= *it
;
2401 if (config
->m_IncludeChildren
) {
2402 if (path
.StartsWith(config
->m_Path
)) {
2403 return config
->m_Handler
;
2406 if (path
== config
->m_Path
) {
2407 return config
->m_Handler
;
2416 /*----------------------------------------------------------------------
2417 | NPT_HttpServer::FindRequestHandlers
2418 +---------------------------------------------------------------------*/
2419 NPT_List
<NPT_HttpRequestHandler
*>
2420 NPT_HttpServer::FindRequestHandlers(NPT_HttpRequest
& request
)
2422 NPT_List
<NPT_HttpRequestHandler
*> handlers
;
2424 for (NPT_List
<HandlerConfig
*>::Iterator it
= m_RequestHandlers
.GetFirstItem();
2427 HandlerConfig
* config
= *it
;
2428 if (config
->m_IncludeChildren
) {
2429 if (request
.GetUrl().GetPath(true).StartsWith(config
->m_Path
)) {
2430 handlers
.Add(config
->m_Handler
);
2433 if (request
.GetUrl().GetPath(true) == config
->m_Path
) {
2434 handlers
.Insert(handlers
.GetFirstItem(), config
->m_Handler
);
2442 /*----------------------------------------------------------------------
2443 | NPT_HttpServer::RespondToClient
2444 +---------------------------------------------------------------------*/
2446 NPT_HttpServer::RespondToClient(NPT_InputStreamReference
& input
,
2447 NPT_OutputStreamReference
& output
,
2448 const NPT_HttpRequestContext
& context
)
2450 NPT_HttpRequest
* request
;
2451 NPT_HttpResponse
* response
= NULL
;
2452 NPT_Result result
= NPT_ERROR_NO_SUCH_ITEM
;
2453 bool terminate_server
= false;
2455 NPT_HttpResponder
responder(input
, output
);
2456 NPT_CHECK_WARNING(responder
.ParseRequest(request
, &context
.GetLocalAddress()));
2457 NPT_LOG_FINE_1("request, path=%s", request
->GetUrl().ToRequestString(true).GetChars());
2459 // prepare the response body
2460 NPT_HttpEntity
* body
= new NPT_HttpEntity();
2462 NPT_HttpRequestHandler
* handler
= FindRequestHandler(*request
);
2464 // create a response object
2465 response
= new NPT_HttpResponse(200, "OK", NPT_HTTP_PROTOCOL_1_0
);
2466 response
->SetEntity(body
);
2468 // ask the handler to setup the response
2469 result
= handler
->SetupResponse(*request
, context
, *response
);
2471 if (result
== NPT_ERROR_NO_SUCH_ITEM
|| handler
== NULL
) {
2472 body
->SetInputStream(NPT_HTTP_DEFAULT_404_HTML
);
2473 body
->SetContentType("text/html");
2474 if (response
== NULL
) {
2475 response
= new NPT_HttpResponse(404, "Not Found", NPT_HTTP_PROTOCOL_1_0
);
2477 response
->SetStatus(404, "Not Found");
2479 response
->SetEntity(body
);
2481 handler
->Completed(NPT_ERROR_NO_SUCH_ITEM
);
2484 } else if (result
== NPT_ERROR_PERMISSION_DENIED
) {
2485 body
->SetInputStream(NPT_HTTP_DEFAULT_403_HTML
);
2486 body
->SetContentType("text/html");
2487 response
->SetStatus(403, "Forbidden");
2488 handler
->Completed(NPT_ERROR_PERMISSION_DENIED
);
2490 } else if (result
== NPT_ERROR_TERMINATED
) {
2491 // mark that we want to exit
2492 terminate_server
= true;
2493 } else if (NPT_FAILED(result
)) {
2494 body
->SetInputStream(NPT_HTTP_DEFAULT_500_HTML
);
2495 body
->SetContentType("text/html");
2496 response
->SetStatus(500, "Internal Error");
2497 handler
->Completed(result
);
2501 // augment the headers with server information
2502 if (m_ServerHeader
.GetLength()) {
2503 response
->GetHeaders().SetHeader(NPT_HTTP_HEADER_SERVER
, m_ServerHeader
, false);
2506 // send the response headers
2507 result
= responder
.SendResponseHeaders(*response
);
2508 if (NPT_FAILED(result
)) {
2509 NPT_LOG_WARNING_2("SendResponseHeaders failed (%d:%s)", result
, NPT_ResultText(result
));
2514 if (request
->GetMethod() != NPT_HTTP_METHOD_HEAD
) {
2516 result
= handler
->SendResponseBody(context
, *response
, *output
);
2518 // send body manually in case there was an error with the handler or no handler was found
2519 NPT_InputStreamReference body_stream
;
2520 body
->GetInputStream(body_stream
);
2521 if (!body_stream
.IsNull()) {
2522 result
= NPT_StreamToStreamCopy(*body_stream
, *output
, 0, body
->GetContentLength());
2523 if (NPT_FAILED(result
)) {
2524 NPT_LOG_INFO_2("NPT_StreamToStreamCopy returned %d (%s)", result
, NPT_ResultText(result
));
2534 // if we need to die, we return an error code
2535 if (NPT_SUCCEEDED(result
) && terminate_server
) result
= NPT_ERROR_TERMINATED
;
2543 handler
->Completed(result
);
2549 /*----------------------------------------------------------------------
2550 | NPT_HttpResponder::NPT_HttpResponder
2551 +---------------------------------------------------------------------*/
2552 NPT_HttpResponder::NPT_HttpResponder(NPT_InputStreamReference
& input
,
2553 NPT_OutputStreamReference
& output
) :
2554 m_Input(new NPT_BufferedInputStream(input
)),
2557 m_Config
.m_IoTimeout
= NPT_HTTP_SERVER_DEFAULT_IO_TIMEOUT
;
2560 /*----------------------------------------------------------------------
2561 | NPT_HttpResponder::~NPT_HttpResponder
2562 +---------------------------------------------------------------------*/
2563 NPT_HttpResponder::~NPT_HttpResponder()
2567 /*----------------------------------------------------------------------
2568 | NPT_HttpServer::Terminate
2569 +---------------------------------------------------------------------*/
2570 void NPT_HttpServer::Terminate()
2575 /*----------------------------------------------------------------------
2576 | NPT_HttpResponder::SetConfig
2577 +---------------------------------------------------------------------*/
2579 NPT_HttpResponder::SetConfig(const Config
& config
)
2586 /*----------------------------------------------------------------------
2587 | NPT_HttpResponder::SetTimeout
2588 +---------------------------------------------------------------------*/
2590 NPT_HttpResponder::SetTimeout(NPT_Timeout io_timeout
)
2592 m_Config
.m_IoTimeout
= io_timeout
;
2597 /*----------------------------------------------------------------------
2598 | NPT_HttpResponder::ParseRequest
2599 +---------------------------------------------------------------------*/
2601 NPT_HttpResponder::ParseRequest(NPT_HttpRequest
*& request
,
2602 const NPT_SocketAddress
* local_address
)
2604 // rebuffer the stream in case we're using a keep-alive connection
2605 m_Input
->SetBufferSize(NPT_BUFFERED_BYTE_STREAM_DEFAULT_SIZE
);
2607 // parse the request
2608 NPT_CHECK_FINE(NPT_HttpRequest::Parse(*m_Input
, local_address
, request
));
2610 // unbuffer the stream
2611 m_Input
->SetBufferSize(0);
2613 // don't create an entity if no body is expected
2614 if (request
->GetMethod() == NPT_HTTP_METHOD_GET
||
2615 request
->GetMethod() == NPT_HTTP_METHOD_HEAD
||
2616 request
->GetMethod() == NPT_HTTP_METHOD_TRACE
) {
2620 // set the entity info
2621 NPT_HttpEntity
* entity
= new NPT_HttpEntity(request
->GetHeaders());
2622 if (entity
->GetTransferEncoding() == NPT_HTTP_TRANSFER_ENCODING_CHUNKED
) {
2623 entity
->SetInputStream(NPT_InputStreamReference(new NPT_HttpChunkedInputStream(m_Input
)));
2625 entity
->SetInputStream(m_Input
);
2627 request
->SetEntity(entity
);
2632 /*----------------------------------------------------------------------
2633 | NPT_HttpResponder::SendResponseHeaders
2634 +---------------------------------------------------------------------*/
2636 NPT_HttpResponder::SendResponseHeaders(NPT_HttpResponse
& response
)
2638 // add default headers
2639 NPT_HttpHeaders
& headers
= response
.GetHeaders();
2640 if (response
.GetProtocol() == NPT_HTTP_PROTOCOL_1_0
) {
2641 headers
.SetHeader(NPT_HTTP_HEADER_CONNECTION
,
2642 "close", false); // set but don't replace
2645 // add computed headers
2646 NPT_HttpEntity
* entity
= response
.GetEntity();
2649 const NPT_String
& content_type
= entity
->GetContentType();
2650 if (!content_type
.IsEmpty()) {
2651 headers
.SetHeader(NPT_HTTP_HEADER_CONTENT_TYPE
, content_type
);
2655 const NPT_String
& content_encoding
= entity
->GetContentEncoding();
2656 if (!content_encoding
.IsEmpty()) {
2657 headers
.SetHeader(NPT_HTTP_HEADER_CONTENT_ENCODING
, content_encoding
);
2660 // transfer encoding
2661 const NPT_String
& transfer_encoding
= entity
->GetTransferEncoding();
2662 if (!transfer_encoding
.IsEmpty()) {
2663 headers
.SetHeader(NPT_HTTP_HEADER_TRANSFER_ENCODING
, transfer_encoding
);
2666 // set the content length if known
2667 if (entity
->ContentLengthIsKnown()) {
2668 headers
.SetHeader(NPT_HTTP_HEADER_CONTENT_LENGTH
,
2669 NPT_String::FromInteger(entity
->GetContentLength()));
2670 } else if (transfer_encoding
.IsEmpty() || transfer_encoding
.Compare(NPT_HTTP_TRANSFER_ENCODING_CHUNKED
, true)) {
2671 // no content length, the only way client will know we're done
2672 // is when we'll close the connection unless it's chunked encoding
2673 headers
.SetHeader(NPT_HTTP_HEADER_CONNECTION
,
2674 "close", true); // set and replace
2677 // force content length to 0 if there is no message body
2678 // (necessary for 1.1 or 1.0 with keep-alive connections)
2679 headers
.SetHeader(NPT_HTTP_HEADER_CONTENT_LENGTH
, "0");
2682 // create a memory stream to buffer the response line and headers
2683 NPT_MemoryStream buffer
;
2685 // emit the response line
2686 NPT_CHECK_WARNING(response
.Emit(buffer
));
2689 NPT_CHECK_WARNING(m_Output
->WriteFully(buffer
.GetData(), buffer
.GetDataSize()));
2694 /*----------------------------------------------------------------------
2695 | NPT_HttpRequestHandler Dynamic Cast Anchor
2696 +---------------------------------------------------------------------*/
2697 NPT_DEFINE_DYNAMIC_CAST_ANCHOR(NPT_HttpRequestHandler
)
2699 /*----------------------------------------------------------------------
2700 | NPT_HttpRequestHandler::SendResponseBody
2701 +---------------------------------------------------------------------*/
2703 NPT_HttpRequestHandler::SendResponseBody(const NPT_HttpRequestContext
& /*context*/,
2704 NPT_HttpResponse
& response
,
2705 NPT_OutputStream
& output
)
2707 NPT_HttpEntity
* entity
= response
.GetEntity();
2708 if (entity
== NULL
) return NPT_SUCCESS
;
2710 NPT_InputStreamReference body_stream
;
2711 entity
->GetInputStream(body_stream
);
2712 if (body_stream
.IsNull()) return NPT_SUCCESS
;
2714 // check for chunked transfer encoding
2715 NPT_OutputStream
* dest
= &output
;
2716 if (entity
->GetTransferEncoding() == NPT_HTTP_TRANSFER_ENCODING_CHUNKED
) {
2717 dest
= new NPT_HttpChunkedOutputStream(output
);
2721 NPT_LOG_FINE_1("sending body stream, %lld bytes", entity
->GetContentLength());
2722 NPT_LargeSize bytes_written
= 0;
2723 NPT_Result result
= NPT_StreamToStreamCopy(*body_stream
, *dest
, 0, entity
->GetContentLength(), &bytes_written
);
2724 if (NPT_FAILED(result
)) {
2725 NPT_LOG_FINE_3("body stream only partially sent, %lld bytes (%d:%s)",
2728 NPT_ResultText(result
));
2731 // flush to write out any buffered data left in chunked output if used
2734 // cleanup (this will send zero size chunk followed by CRLF)
2735 if (dest
!= &output
) delete dest
;
2740 /*----------------------------------------------------------------------
2741 | NPT_HttpStaticRequestHandler::NPT_HttpStaticRequestHandler
2742 +---------------------------------------------------------------------*/
2743 NPT_HttpStaticRequestHandler::NPT_HttpStaticRequestHandler(const void* data
,
2745 const char* mime_type
,
2747 m_MimeType(mime_type
),
2748 m_Buffer(data
, size
, copy
)
2751 /*----------------------------------------------------------------------
2752 | NPT_HttpStaticRequestHandler::NPT_HttpStaticRequestHandler
2753 +---------------------------------------------------------------------*/
2754 NPT_HttpStaticRequestHandler::NPT_HttpStaticRequestHandler(const char* document
,
2755 const char* mime_type
,
2757 m_MimeType(mime_type
),
2758 m_Buffer(document
, NPT_StringLength(document
), copy
)
2761 /*----------------------------------------------------------------------
2762 | NPT_HttpStaticRequestHandler::SetupResponse
2763 +---------------------------------------------------------------------*/
2765 NPT_HttpStaticRequestHandler::SetupResponse(NPT_HttpRequest
& /*request*/,
2766 const NPT_HttpRequestContext
& /*context*/,
2767 NPT_HttpResponse
& response
)
2769 NPT_HttpEntity
* entity
= response
.GetEntity();
2770 if (entity
== NULL
) return NPT_ERROR_INVALID_STATE
;
2772 entity
->SetContentType(m_MimeType
);
2773 entity
->SetInputStream(m_Buffer
.GetData(), m_Buffer
.GetDataSize());
2778 const NPT_HttpFileRequestHandler_DefaultFileTypeMapEntry
2779 NPT_HttpFileRequestHandler_DefaultFileTypeMap
[] = {
2780 {"xml", "text/xml; charset=\"utf-8\"" },
2781 {"htm", "text/html" },
2782 {"html", "text/html" },
2783 {"c", "text/plain"},
2784 {"h", "text/plain"},
2785 {"txt", "text/plain"},
2786 {"css", "text/css" },
2787 {"manifest", "text/cache-manifest"},
2788 {"gif", "image/gif" },
2789 {"thm", "image/jpeg"},
2790 {"png", "image/png"},
2791 {"tif", "image/tiff"},
2792 {"tiff", "image/tiff"},
2793 {"jpg", "image/jpeg"},
2794 {"jpeg", "image/jpeg"},
2795 {"jpe", "image/jpeg"},
2796 {"jp2", "image/jp2" },
2797 {"png", "image/png" },
2798 {"bmp", "image/bmp" },
2799 {"aif", "audio/x-aiff"},
2800 {"aifc", "audio/x-aiff"},
2801 {"aiff", "audio/x-aiff"},
2802 {"flac", "audio/x-flac"},
2803 {"mka", "audio/x-matroska"},
2804 {"mpa", "audio/mpeg"},
2805 {"mp2", "audio/mpeg"},
2806 {"mp3", "audio/mpeg"},
2807 {"m4a", "audio/mp4"},
2808 {"wma", "audio/x-ms-wma"},
2809 {"wav", "audio/x-wav"},
2810 {"mkv", "video/x-matroska"},
2811 {"mpeg", "video/mpeg"},
2812 {"mpg", "video/mpeg"},
2813 {"mp4", "video/mp4"},
2814 {"m4v", "video/mp4"},
2815 {"ts", "video/MP2T"}, // RFC 3555
2816 {"mpegts", "video/MP2T"},
2817 {"mov", "video/quicktime"},
2818 {"qt", "video/quicktime"},
2819 {"wmv", "video/x-ms-wmv"},
2820 {"wtv", "video/x-ms-wmv"},
2821 {"asf", "video/x-ms-asf"},
2822 {"mkv", "video/x-matroska"},
2823 {"mk3d", "video/x-matroska-3d"},
2824 {"flv", "video/x-flv"},
2825 {"avi", "video/x-msvideo"},
2826 {"divx", "video/x-msvideo"},
2827 {"xvid", "video/x-msvideo"},
2828 {"doc", "application/msword"},
2829 {"js", "application/javascript"},
2830 {"m3u8", "application/x-mpegURL"},
2831 {"pdf", "application/pdf"},
2832 {"ps", "application/postscript"},
2833 {"eps", "application/postscript"},
2834 {"zip", "application/zip"}
2837 /*----------------------------------------------------------------------
2838 | NPT_HttpFileRequestHandler::NPT_HttpFileRequestHandler
2839 +---------------------------------------------------------------------*/
2840 NPT_HttpFileRequestHandler::NPT_HttpFileRequestHandler(const char* url_root
,
2841 const char* file_root
,
2843 const char* auto_index
) :
2844 m_UrlRoot(url_root
),
2845 m_FileRoot(file_root
),
2846 m_DefaultMimeType("text/html"),
2847 m_UseDefaultFileTypeMap(true),
2848 m_AutoDir(auto_dir
),
2849 m_AutoIndex(auto_index
)
2853 /*----------------------------------------------------------------------
2854 | helper functions FIXME: need to move these to a separate module
2855 +---------------------------------------------------------------------*/
2857 _utf8_decode(const char** str
)
2860 NPT_UInt32 min_value
;
2861 unsigned int bytes_left
;
2865 } else if ((**str
& 0x80) == 0x00) {
2869 } else if ((**str
& 0xE0) == 0xC0) {
2870 result
= *(*str
)++ & 0x1F;
2873 } else if ((**str
& 0xF0) == 0xE0) {
2874 result
= *(*str
)++ & 0x0F;
2877 } else if ((**str
& 0xF8) == 0xF0) {
2878 result
= *(*str
)++ & 0x07;
2880 min_value
= 0x10000;
2885 while (bytes_left
--) {
2886 if (**str
== 0 || (**str
& 0xC0) != 0x80) return ~0;
2887 result
= (result
<< 6) | (*(*str
)++ & 0x3F);
2890 if (result
< min_value
|| (result
& 0xFFFFF800) == 0xD800 || result
> 0x10FFFF) {
2897 /*----------------------------------------------------------------------
2899 +---------------------------------------------------------------------*/
2901 NPT_HtmlEncode(const char* str
, const char* chars
)
2906 if (str
== NULL
) return encoded
;
2908 // reserve at least the size of the current uri
2909 encoded
.Reserve(NPT_StringLength(str
));
2911 // process each character
2913 NPT_UInt32 c
= _utf8_decode(&str
);
2914 bool encode
= false;
2915 if (c
< ' ' || c
> '~') {
2918 const char* match
= chars
;
2920 if (c
== (NPT_UInt32
)*match
) {
2931 unsigned int len
= 0;
2933 NPT_ByteToHex((unsigned char)(c
>>24), &hex
[0], true);
2934 NPT_ByteToHex((unsigned char)(c
>>16), &hex
[2], true);
2937 NPT_ByteToHex((unsigned char)(c
>>8), &hex
[len
], true);
2938 NPT_ByteToHex((unsigned char)(c
), &hex
[len
+2], true);
2940 encoded
.Append(hex
, len
+5);
2942 // no encoding required
2950 /*----------------------------------------------------------------------
2951 | NPT_HttpFileRequestHandler::SetupResponse
2952 +---------------------------------------------------------------------*/
2954 NPT_HttpFileRequestHandler::SetupResponse(NPT_HttpRequest
& request
,
2955 const NPT_HttpRequestContext
& /* context */,
2956 NPT_HttpResponse
& response
)
2958 NPT_HttpEntity
* entity
= response
.GetEntity();
2959 if (entity
== NULL
) return NPT_ERROR_INVALID_STATE
;
2962 if (request
.GetMethod() != NPT_HTTP_METHOD_GET
&&
2963 request
.GetMethod() != NPT_HTTP_METHOD_HEAD
) {
2964 response
.SetStatus(405, "Method Not Allowed");
2968 // set some default headers
2969 response
.GetHeaders().SetHeader(NPT_HTTP_HEADER_ACCEPT_RANGES
, "bytes");
2971 // declare HTTP/1.1 if the client asked for it
2972 if (request
.GetProtocol() == NPT_HTTP_PROTOCOL_1_1
) {
2973 response
.SetProtocol(NPT_HTTP_PROTOCOL_1_1
);
2976 // TODO: we need to normalize the request path
2978 // check that the request's path is an entry under the url root
2979 if (!request
.GetUrl().GetPath(true).StartsWith(m_UrlRoot
)) {
2980 return NPT_ERROR_INVALID_PARAMETERS
;
2983 // compute the filename
2984 NPT_String filename
= m_FileRoot
;
2985 NPT_String relative_path
= NPT_Url::PercentDecode(request
.GetUrl().GetPath().GetChars()+m_UrlRoot
.GetLength());
2987 filename
+= relative_path
;
2988 NPT_LOG_FINE_1("filename = %s", filename
.GetChars());
2990 // get info about the file
2992 NPT_File::GetInfo(filename
, &info
);
2994 // check if this is a directory
2995 if (info
.m_Type
== NPT_FileInfo::FILE_TYPE_DIRECTORY
) {
2996 NPT_LOG_FINE("file is a DIRECTORY");
2998 if (m_AutoIndex
.GetLength()) {
2999 NPT_LOG_FINE("redirecting to auto-index");
3000 filename
+= NPT_FilePath::Separator
;
3001 filename
+= m_AutoIndex
;
3002 if (NPT_File::Exists(filename
)) {
3003 NPT_String location
= m_UrlRoot
+"/"+m_AutoIndex
;
3004 response
.SetStatus(302, "Found");
3005 response
.GetHeaders().SetHeader(NPT_HTTP_HEADER_LOCATION
, location
);
3007 return NPT_ERROR_PERMISSION_DENIED
;
3010 NPT_LOG_FINE("doing auto-dir");
3012 // get the dir entries
3013 NPT_List
<NPT_String
> entries
;
3014 NPT_File::ListDir(filename
, entries
);
3017 html
.Reserve(1024+128*entries
.GetItemCount());
3019 NPT_String html_dirname
= NPT_HtmlEncode(relative_path
, "<>&");
3020 html
+= "<hmtl><head><title>Directory Listing for /";
3021 html
+= html_dirname
;
3022 html
+= "</title></head><body>";
3023 html
+= "<h2>Directory Listing for /";
3024 html
+= html_dirname
;
3025 html
+= "</h2><hr><ul>\r\n";
3026 NPT_String url_base_path
= NPT_HtmlEncode(request
.GetUrl().GetPath(), "<>&\"");
3028 for (NPT_List
<NPT_String
>::Iterator i
= entries
.GetFirstItem();
3031 NPT_String url_filename
= NPT_HtmlEncode(*i
, "<>&");
3032 html
+= "<li><a href=\"";
3033 html
+= url_base_path
;
3034 if (!url_base_path
.EndsWith("/")) html
+= "/";
3035 html
+= url_filename
;
3037 html
+=url_filename
;
3039 NPT_String full_path
= filename
;
3042 NPT_File::GetInfo(full_path
, &info
);
3043 if (info
.m_Type
== NPT_FileInfo::FILE_TYPE_DIRECTORY
) html
+= "/";
3045 html
+= "</a><br>\r\n";
3047 html
+= "</ul></body></html>";
3049 entity
->SetContentType("text/html");
3050 entity
->SetInputStream(html
);
3054 return NPT_ERROR_PERMISSION_DENIED
;
3059 NPT_File
file(filename
);
3060 NPT_Result result
= file
.Open(NPT_FILE_OPEN_MODE_READ
);
3061 if (NPT_FAILED(result
)) {
3062 NPT_LOG_FINE("file not found");
3063 return NPT_ERROR_NO_SUCH_ITEM
;
3065 NPT_InputStreamReference stream
;
3066 file
.GetInputStream(stream
);
3068 // check for range requests
3069 const NPT_String
* range_spec
= request
.GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_RANGE
);
3071 // setup entity body
3072 NPT_CHECK(SetupResponseBody(response
, stream
, range_spec
));
3074 // set the response body
3075 entity
->SetContentType(GetContentType(filename
));
3080 /*----------------------------------------------------------------------
3081 | NPT_HttpFileRequestHandler::SetupResponseBody
3082 +---------------------------------------------------------------------*/
3084 NPT_HttpFileRequestHandler::SetupResponseBody(NPT_HttpResponse
& response
,
3085 NPT_InputStreamReference
& stream
,
3086 const NPT_String
* range_spec
/* = NULL */)
3088 NPT_HttpEntity
* entity
= response
.GetEntity();
3089 if (entity
== NULL
) return NPT_ERROR_INVALID_STATE
;
3092 const NPT_String
* accept_range
= response
.GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_ACCEPT_RANGES
);
3094 if (response
.GetEntity()->GetTransferEncoding() == NPT_HTTP_TRANSFER_ENCODING_CHUNKED
||
3095 (accept_range
&& accept_range
->Compare("bytes"))) {
3096 NPT_LOG_FINE("range request not supported");
3097 response
.SetStatus(416, "Requested Range Not Satisfiable");
3101 // measure the stream size
3102 bool has_stream_size
= false;
3103 NPT_LargeSize stream_size
= 0;
3104 NPT_Result result
= stream
->GetSize(stream_size
);
3105 if (NPT_SUCCEEDED(result
)) {
3106 has_stream_size
= true;
3107 NPT_LOG_FINE_1("body size=%lld", stream_size
);
3108 if (stream_size
== 0) return NPT_SUCCESS
;
3111 if (!range_spec
->StartsWith("bytes=")) {
3112 NPT_LOG_FINE("unknown range spec");
3113 response
.SetStatus(400, "Bad Request");
3116 NPT_String valid_range
;
3117 NPT_String
range(range_spec
->GetChars()+6);
3118 if (range
.Find(',') >= 0) {
3119 NPT_LOG_FINE("multi-range requests not supported");
3120 if (has_stream_size
) {
3121 valid_range
= "bytes */";
3122 valid_range
+= NPT_String::FromInteger(stream_size
);
3123 response
.GetHeaders().SetHeader(NPT_HTTP_HEADER_CONTENT_RANGE
, valid_range
.GetChars());
3125 response
.SetStatus(416, "Requested Range Not Satisfiable");
3128 int sep
= range
.Find('-');
3129 NPT_UInt64 range_start
= 0;
3130 NPT_UInt64 range_end
= 0;
3131 bool has_start
= false;
3132 bool has_end
= false;
3133 bool satisfied
= false;
3135 NPT_LOG_FINE("invalid syntax");
3136 response
.SetStatus(400, "Bad Request");
3139 if ((unsigned int)sep
+1 < range
.GetLength()) {
3140 result
= NPT_ParseInteger64(range
.GetChars()+sep
+1, range_end
);
3141 if (NPT_FAILED(result
)) {
3142 NPT_LOG_FINE("failed to parse range end");
3145 range
.SetLength(sep
);
3149 result
= range
.ToInteger64(range_start
);
3150 if (NPT_FAILED(result
)) {
3151 NPT_LOG_FINE("failed to parse range start");
3157 if (!has_stream_size
) {
3158 if (has_start
&& range_start
== 0 && !has_end
) {
3159 bool update_content_length
= (entity
->GetTransferEncoding() != NPT_HTTP_TRANSFER_ENCODING_CHUNKED
);
3160 // use the whole file stream as a body
3161 return entity
->SetInputStream(stream
, update_content_length
);
3163 NPT_LOG_WARNING_2("file.GetSize() failed (%d:%s)", result
, NPT_ResultText(result
));
3164 NPT_LOG_FINE("range request not supported");
3165 response
.SetStatus(416, "Requested Range Not Satisfiable");
3171 // some clients sends incorrect range_end equal to size
3172 // we try to handle it
3173 if (!has_end
|| range_end
== stream_size
) range_end
= stream_size
-1;
3176 if (range_end
<= stream_size
) {
3177 range_start
= stream_size
-range_end
;
3178 range_end
= stream_size
-1;
3182 NPT_LOG_FINE_2("final range: start=%lld, end=%lld", range_start
, range_end
);
3183 if (range_start
> range_end
) {
3184 NPT_LOG_FINE("invalid range");
3185 response
.SetStatus(400, "Bad Request");
3187 } else if (range_end
>= stream_size
) {
3188 response
.SetStatus(416, "Requested Range Not Satisfiable");
3189 NPT_LOG_FINE("out of range");
3195 if (satisfied
&& range_start
!= 0) {
3196 // seek in the stream
3197 result
= stream
->Seek(range_start
);
3198 if (NPT_FAILED(result
)) {
3199 NPT_LOG_WARNING_2("stream.Seek() failed (%d:%s)", result
, NPT_ResultText(result
));
3204 if (!valid_range
.IsEmpty()) response
.GetHeaders().SetHeader(NPT_HTTP_HEADER_CONTENT_RANGE
, valid_range
.GetChars());
3205 response
.SetStatus(416, "Requested Range Not Satisfiable");
3209 // use a portion of the file stream as a body
3210 entity
->SetInputStream(stream
, false);
3211 entity
->SetContentLength(range_end
-range_start
+1);
3212 response
.SetStatus(206, "Partial Content");
3213 valid_range
= "bytes ";
3214 valid_range
+= NPT_String::FromInteger(range_start
);
3216 valid_range
+= NPT_String::FromInteger(range_end
);
3218 valid_range
+= NPT_String::FromInteger(stream_size
);
3219 response
.GetHeaders().SetHeader(NPT_HTTP_HEADER_CONTENT_RANGE
, valid_range
.GetChars());
3221 bool update_content_length
= (entity
->GetTransferEncoding() != NPT_HTTP_TRANSFER_ENCODING_CHUNKED
);
3222 // use the whole file stream as a body
3223 entity
->SetInputStream(stream
, update_content_length
);
3229 /*----------------------------------------------------------------------
3230 | NPT_HttpFileRequestHandler::GetContentType
3231 +---------------------------------------------------------------------*/
3233 NPT_HttpFileRequestHandler::GetDefaultContentType(const char* extension
)
3235 for (unsigned int i
=0; i
<NPT_ARRAY_SIZE(NPT_HttpFileRequestHandler_DefaultFileTypeMap
); i
++) {
3236 if (NPT_String::Compare(extension
, NPT_HttpFileRequestHandler_DefaultFileTypeMap
[i
].extension
, true) == 0) {
3237 const char* type
= NPT_HttpFileRequestHandler_DefaultFileTypeMap
[i
].mime_type
;
3238 NPT_LOG_FINE_1("using type from default list: %s", type
);
3246 /*----------------------------------------------------------------------
3247 | NPT_HttpFileRequestHandler::GetContentType
3248 +---------------------------------------------------------------------*/
3250 NPT_HttpFileRequestHandler::GetContentType(const NPT_String
& filename
)
3252 int last_dot
= filename
.ReverseFind('.');
3254 NPT_String extension
= filename
.GetChars()+last_dot
+1;
3255 extension
.MakeLowercase();
3257 NPT_LOG_FINE_1("extension=%s", extension
.GetChars());
3259 NPT_String
* mime_type
;
3260 if (NPT_SUCCEEDED(m_FileTypeMap
.Get(extension
, mime_type
))) {
3261 NPT_LOG_FINE_1("found mime type in map: %s", mime_type
->GetChars());
3262 return mime_type
->GetChars();
3265 // not found, look in the default map if necessary
3266 if (m_UseDefaultFileTypeMap
) {
3267 const char* type
= NPT_HttpFileRequestHandler::GetDefaultContentType(extension
);
3268 if (type
) return type
;
3272 NPT_LOG_FINE("using default mime type");
3273 return m_DefaultMimeType
;
3276 /*----------------------------------------------------------------------
3277 | NPT_HttpChunkedInputStream::NPT_HttpChunkedInputStream
3278 +---------------------------------------------------------------------*/
3279 NPT_HttpChunkedInputStream::NPT_HttpChunkedInputStream(
3280 NPT_BufferedInputStreamReference
& stream
) :
3282 m_CurrentChunkSize(0),
3287 /*----------------------------------------------------------------------
3288 | NPT_HttpChunkedInputStream::~NPT_HttpChunkedInputStream
3289 +---------------------------------------------------------------------*/
3290 NPT_HttpChunkedInputStream::~NPT_HttpChunkedInputStream()
3294 /*----------------------------------------------------------------------
3295 | NPT_HttpChunkedInputStream::NPT_HttpChunkedInputStream
3296 +---------------------------------------------------------------------*/
3298 NPT_HttpChunkedInputStream::Read(void* buffer
,
3299 NPT_Size bytes_to_read
,
3300 NPT_Size
* bytes_read
/* = NULL */)
3302 // set the initial state of return values
3303 if (bytes_read
) *bytes_read
= 0;
3305 // check for end of stream
3306 if (m_Eos
) return NPT_ERROR_EOS
;
3309 if (bytes_to_read
== 0) return NPT_SUCCESS
;
3311 // read next chunk size if needed
3312 if (m_CurrentChunkSize
== 0) {
3314 m_Source
->SetBufferSize(4096);
3316 NPT_String size_line
;
3317 NPT_CHECK_FINE(m_Source
->ReadLine(size_line
));
3319 // decode size (in hex)
3320 m_CurrentChunkSize
= 0;
3321 if (size_line
.GetLength() < 1) {
3322 NPT_LOG_WARNING("empty chunk size line");
3323 return NPT_ERROR_INVALID_FORMAT
;
3325 const char* size_hex
= size_line
.GetChars();
3326 while (*size_hex
!= '\0' &&
3329 *size_hex
!= '\r' &&
3330 *size_hex
!= '\n') {
3331 int nibble
= NPT_HexToNibble(*size_hex
);
3333 NPT_LOG_WARNING_1("invalid chunk size format (%s)", size_line
.GetChars());
3334 return NPT_ERROR_INVALID_FORMAT
;
3336 m_CurrentChunkSize
= (m_CurrentChunkSize
<<4)|nibble
;
3339 NPT_LOG_FINEST_1("start of chunk, size=%d", m_CurrentChunkSize
);
3342 if (m_CurrentChunkSize
== 0) {
3343 NPT_LOG_FINEST("end of chunked stream, reading trailers");
3345 // read footers until empty line
3348 NPT_CHECK_FINE(m_Source
->ReadLine(footer
));
3349 } while (!footer
.IsEmpty());
3352 NPT_LOG_FINEST("end of chunked stream, done");
3353 return NPT_ERROR_EOS
;
3357 m_Source
->SetBufferSize(0);
3360 // read no more than what's left in chunk
3361 NPT_Size chunk_bytes_read
;
3362 if (bytes_to_read
> m_CurrentChunkSize
) bytes_to_read
= m_CurrentChunkSize
;
3363 NPT_CHECK_FINE(m_Source
->Read(buffer
, bytes_to_read
, &chunk_bytes_read
));
3365 // ready to go to next chunk?
3366 m_CurrentChunkSize
-= chunk_bytes_read
;
3367 if (m_CurrentChunkSize
== 0) {
3368 NPT_LOG_FINEST("reading end of chunk");
3370 // when a chunk is finished, a \r\n follows
3372 NPT_CHECK_FINE(m_Source
->ReadFully(newline
, 2));
3373 if (newline
[0] != '\r' || newline
[1] != '\n') {
3374 NPT_LOG_WARNING("invalid end of chunk (expected \\r\\n)");
3375 return NPT_ERROR_INVALID_FORMAT
;
3379 // update output params
3380 if (bytes_read
) *bytes_read
= chunk_bytes_read
;
3385 /*----------------------------------------------------------------------
3386 | NPT_HttpChunkedInputStream::Seek
3387 +---------------------------------------------------------------------*/
3389 NPT_HttpChunkedInputStream::Seek(NPT_Position
/*offset*/)
3391 return NPT_ERROR_NOT_SUPPORTED
;
3394 /*----------------------------------------------------------------------
3395 | NPT_HttpChunkedInputStream::Tell
3396 +---------------------------------------------------------------------*/
3398 NPT_HttpChunkedInputStream::Tell(NPT_Position
& offset
)
3401 return NPT_ERROR_NOT_SUPPORTED
;
3404 /*----------------------------------------------------------------------
3405 | NPT_HttpChunkedInputStream::GetSize
3406 +---------------------------------------------------------------------*/
3408 NPT_HttpChunkedInputStream::GetSize(NPT_LargeSize
& size
)
3410 return m_Source
->GetSize(size
);
3413 /*----------------------------------------------------------------------
3414 | NPT_HttpChunkedInputStream::GetAvailable
3415 +---------------------------------------------------------------------*/
3417 NPT_HttpChunkedInputStream::GetAvailable(NPT_LargeSize
& available
)
3419 return m_Source
->GetAvailable(available
);
3422 /*----------------------------------------------------------------------
3423 | NPT_HttpChunkedOutputStream::NPT_HttpChunkedOutputStream
3424 +---------------------------------------------------------------------*/
3425 NPT_HttpChunkedOutputStream::NPT_HttpChunkedOutputStream(NPT_OutputStream
& stream
) :
3430 /*----------------------------------------------------------------------
3431 | NPT_HttpChunkedOutputStream::~NPT_HttpChunkedOutputStream
3432 +---------------------------------------------------------------------*/
3433 NPT_HttpChunkedOutputStream::~NPT_HttpChunkedOutputStream()
3435 // zero size chunk followed by CRLF (no trailer)
3436 m_Stream
.WriteFully("0" NPT_HTTP_LINE_TERMINATOR NPT_HTTP_LINE_TERMINATOR
, 5);
3439 /*----------------------------------------------------------------------
3440 | NPT_HttpChunkedOutputStream::Write
3441 +---------------------------------------------------------------------*/
3443 NPT_HttpChunkedOutputStream::Write(const void* buffer
,
3444 NPT_Size bytes_to_write
,
3445 NPT_Size
* bytes_written
)
3448 if (bytes_written
) *bytes_written
= 0;
3451 if (bytes_to_write
== 0) return NPT_SUCCESS
;
3453 // write the chunk header
3457 char* c
= &size
[14];
3458 unsigned int char_count
= 2;
3459 unsigned int value
= bytes_to_write
;
3461 unsigned int digit
= (unsigned int)(value
%16);
3465 *--c
= 'A'+digit
-10;
3470 NPT_Result result
= m_Stream
.WriteFully(c
, char_count
);
3471 if (NPT_FAILED(result
)) return result
;
3473 // write the chunk data
3474 result
= m_Stream
.WriteFully(buffer
, bytes_to_write
);
3475 if (NPT_FAILED(result
)) return result
;
3478 result
= m_Stream
.WriteFully(NPT_HTTP_LINE_TERMINATOR
, 2);
3479 if (NPT_SUCCEEDED(result
) && bytes_written
) {
3480 *bytes_written
= bytes_to_write
;