Files
haiku-beta6/src/kits/network/libnetapi/HttpRequest.cpp
T
Adrien Destugues 3d864cd870 Remove B_PROT_* and related code
Use standard error codes instead.
This allows using error code returned by the underlying functions
directly, and makes it possible to use strerror for debugging. So, we
can also remove StatusString() from the various *Request classes.
2014-01-13 08:05:32 +01:00

915 lines
21 KiB
C++

/*
* Copyright 2010-2013 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Christophe Huriaux, [email protected]
* Niels Sascha Reedijk, [email protected]
*/
#include <HttpRequest.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <cstdlib>
#include <deque>
#include <new>
#include <Debug.h>
#include <File.h>
#include <Socket.h>
#include <SecureSocket.h>
static const int32 kHttpBufferSize = 4096;
BHttpRequest::BHttpRequest(const BUrl& url, bool ssl, const char* protocolName,
BUrlProtocolListener* listener, BUrlContext* context)
:
BUrlRequest(url, listener, context, "BUrlProtocol.HTTP", protocolName),
fSocket(NULL),
fSSL(ssl),
fRequestMethod(B_HTTP_GET),
fHttpVersion(B_HTTP_11),
fResult(url),
fRequestStatus(kRequestInitialState),
fOptHeaders(NULL),
fOptPostFields(NULL),
fOptInputData(NULL),
fOptInputDataSize(-1),
fOptFollowLocation(true)
{
_ResetOptions();
if (ssl)
fSocket = new BSecureSocket();
else
fSocket = new BSocket();
}
BHttpRequest::~BHttpRequest()
{
Stop();
delete fSocket;
delete fOptInputData;
delete fOptHeaders;
delete fOptPostFields;
}
void
BHttpRequest::SetMethod(const char* const method)
{
fRequestMethod = method;
}
void
BHttpRequest::SetFollowLocation(bool follow)
{
fOptFollowLocation = follow;
}
void
BHttpRequest::SetMaxRedirections(int8 redirections)
{
fOptMaxRedirs = redirections;
}
void
BHttpRequest::SetReferrer(const BString& referrer)
{
fOptReferer = referrer;
}
void
BHttpRequest::SetUserAgent(const BString& agent)
{
fOptUserAgent = agent;
}
void
BHttpRequest::SetDiscardData(bool discard)
{
fOptDiscardData = discard;
}
void
BHttpRequest::SetDisableListener(bool disable)
{
fOptDisableListener = disable;
}
void
BHttpRequest::SetAutoReferrer(bool enable)
{
fOptAutoReferer = enable;
}
void
BHttpRequest::SetHeaders(const BHttpHeaders& headers)
{
AdoptHeaders(new BHttpHeaders(headers));
}
void
BHttpRequest::AdoptHeaders(BHttpHeaders* const headers)
{
delete fOptHeaders;
fOptHeaders = headers;
}
void
BHttpRequest::SetPostFields(const BHttpForm& fields)
{
AdoptPostFields(new BHttpForm(fields));
}
void
BHttpRequest::AdoptPostFields(BHttpForm* const fields)
{
delete fOptPostFields;
fOptPostFields = fields;
if (fOptPostFields != NULL)
fRequestMethod = B_HTTP_POST;
}
void
BHttpRequest::AdoptInputData(BDataIO* const data, const ssize_t size)
{
delete fOptInputData;
fOptInputData = data;
fOptInputDataSize = size;
}
void
BHttpRequest::SetUserName(const BString& name)
{
fOptUsername = name;
}
void
BHttpRequest::SetPassword(const BString& password)
{
fOptPassword = password;
}
/*static*/ bool
BHttpRequest::IsInformationalStatusCode(int16 code)
{
return (code >= B_HTTP_STATUS__INFORMATIONAL_BASE)
&& (code < B_HTTP_STATUS__INFORMATIONAL_END);
}
/*static*/ bool
BHttpRequest::IsSuccessStatusCode(int16 code)
{
return (code >= B_HTTP_STATUS__SUCCESS_BASE)
&& (code < B_HTTP_STATUS__SUCCESS_END);
}
/*static*/ bool
BHttpRequest::IsRedirectionStatusCode(int16 code)
{
return (code >= B_HTTP_STATUS__REDIRECTION_BASE)
&& (code < B_HTTP_STATUS__REDIRECTION_END);
}
/*static*/ bool
BHttpRequest::IsClientErrorStatusCode(int16 code)
{
return (code >= B_HTTP_STATUS__CLIENT_ERROR_BASE)
&& (code < B_HTTP_STATUS__CLIENT_ERROR_END);
}
/*static*/ bool
BHttpRequest::IsServerErrorStatusCode(int16 code)
{
return (code >= B_HTTP_STATUS__SERVER_ERROR_BASE)
&& (code < B_HTTP_STATUS__SERVER_ERROR_END);
}
/*static*/ int16
BHttpRequest::StatusCodeClass(int16 code)
{
if (BHttpRequest::IsInformationalStatusCode(code))
return B_HTTP_STATUS_CLASS_INFORMATIONAL;
else if (BHttpRequest::IsSuccessStatusCode(code))
return B_HTTP_STATUS_CLASS_SUCCESS;
else if (BHttpRequest::IsRedirectionStatusCode(code))
return B_HTTP_STATUS_CLASS_REDIRECTION;
else if (BHttpRequest::IsClientErrorStatusCode(code))
return B_HTTP_STATUS_CLASS_CLIENT_ERROR;
else if (BHttpRequest::IsServerErrorStatusCode(code))
return B_HTTP_STATUS_CLASS_SERVER_ERROR;
return B_HTTP_STATUS_CLASS_INVALID;
}
const BUrlResult&
BHttpRequest::Result() const
{
return fResult;
}
status_t
BHttpRequest::Stop()
{
fSocket->Disconnect();
// Unlock any pending connect, read or write operation.
return BUrlRequest::Stop();
}
void
BHttpRequest::_ResetOptions()
{
delete fOptPostFields;
delete fOptHeaders;
fOptFollowLocation = true;
fOptMaxRedirs = 8;
fOptReferer = "";
fOptUserAgent = "Services Kit (Haiku)";
fOptUsername = "";
fOptPassword = "";
fOptAuthMethods = B_HTTP_AUTHENTICATION_BASIC | B_HTTP_AUTHENTICATION_DIGEST
| B_HTTP_AUTHENTICATION_IE_DIGEST;
fOptHeaders = NULL;
fOptPostFields = NULL;
fOptSetCookies = true;
fOptDiscardData = false;
fOptDisableListener = false;
fOptAutoReferer = true;
}
status_t
BHttpRequest::_ProtocolLoop()
{
// Initialize the request redirection loop
int8 maxRedirs = fOptMaxRedirs;
bool newRequest;
do {
newRequest = false;
// Result reset
fOutputHeaders.Clear();
fHeaders.Clear();
_ResultHeaders().Clear();
if (!_ResolveHostName()) {
_EmitDebug(B_URL_PROTOCOL_DEBUG_ERROR,
"Unable to resolve hostname (%s), aborting.",
fUrl.Host().String());
return B_SERVER_NOT_FOUND;
}
status_t requestStatus = _MakeRequest();
if (requestStatus != B_OK)
return requestStatus;
// Prepare the referer for the next request if needed
if (fOptAutoReferer)
fOptReferer = fUrl.UrlString();
switch (StatusCodeClass(fResult.StatusCode())) {
case B_HTTP_STATUS_CLASS_INFORMATIONAL:
// Header 100:continue should have been
// handled in the _MakeRequest read loop
break;
case B_HTTP_STATUS_CLASS_SUCCESS:
break;
case B_HTTP_STATUS_CLASS_REDIRECTION:
// Redirection has been explicitly disabled
if (!fOptFollowLocation)
break;
// TODO: Some browsers seems to translate POST requests to
// GET when following a 302 redirection
if (fResult.StatusCode() == B_HTTP_STATUS_MOVED_PERMANENTLY) {
BString locationUrl = fHeaders["Location"];
fUrl = BUrl(fUrl, locationUrl);
if (--maxRedirs > 0) {
newRequest = true;
_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT,
"Following: %s\n",
fUrl.UrlString().String());
}
}
break;
case B_HTTP_STATUS_CLASS_CLIENT_ERROR:
if (fResult.StatusCode() == B_HTTP_STATUS_UNAUTHORIZED) {
BHttpAuthentication* authentication
= &fContext->GetAuthentication(fUrl);
status_t status = B_OK;
if (authentication->Method() == B_HTTP_AUTHENTICATION_NONE) {
// There is no authentication context for this
// url yet, so let's create one.
authentication = new BHttpAuthentication();
status = authentication->Initialize(
fHeaders["WWW-Authenticate"]);
fContext->AddAuthentication(fUrl, authentication);
}
newRequest = false;
if (fOptUsername.Length() > 0 && status == B_OK) {
// If we received an username and password, add them
// to the request. This will either change the
// credentials for an existing request, or set them
// for a new one we created just above.
//
// If this request handles HTTP redirections, it will
// also automatically retry connecting and send the
// login information.
authentication->SetUserName(fOptUsername);
authentication->SetPassword(fOptPassword);
newRequest = true;
}
}
break;
case B_HTTP_STATUS_CLASS_SERVER_ERROR:
break;
default:
case B_HTTP_STATUS_CLASS_INVALID:
break;
}
} while (newRequest);
_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT,
"%ld headers and %ld bytes of data remaining",
fHeaders.CountHeaders(), fInputBuffer.Size());
if (fResult.StatusCode() == 404)
return B_RESOURCE_NOT_FOUND;
return B_OK;
}
bool
BHttpRequest::_ResolveHostName()
{
_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Resolving %s",
fUrl.UrlString().String());
uint16_t port;
if (fUrl.HasPort())
port = fUrl.Port();
else
port = fSSL ? 443 : 80;
fRemoteAddr = BNetworkAddress(AF_INET, fUrl.Host(), port);
if (fRemoteAddr.InitCheck() != B_OK)
return false;
//! ProtocolHook:HostnameResolved
if (fListener != NULL)
fListener->HostnameResolved(this, fRemoteAddr.ToString().String());
_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Hostname resolved to: %s",
fRemoteAddr.ToString().String());
return true;
}
status_t
BHttpRequest::_MakeRequest()
{
_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Connection to %s on port %d.",
fUrl.Authority().String(), fRemoteAddr.Port());
status_t connectError = fSocket->Connect(fRemoteAddr);
if (connectError != B_OK) {
_EmitDebug(B_URL_PROTOCOL_DEBUG_ERROR, "Socket connection error %s",
strerror(connectError));
return connectError;
}
//! ProtocolHook:ConnectionOpened
if (fListener != NULL)
fListener->ConnectionOpened(this);
_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Connection opened, sending request.");
_SendRequest();
_SendHeaders();
fSocket->Write("\r\n", 2);
_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Request sent.");
if (fRequestMethod == B_HTTP_POST && fOptPostFields != NULL) {
if (fOptPostFields->GetFormType() != B_HTTP_FORM_MULTIPART) {
BString outputBuffer = fOptPostFields->RawData();
_EmitDebug(B_URL_PROTOCOL_DEBUG_TRANSFER_OUT,
"%s", outputBuffer.String());
fSocket->Write(outputBuffer.String(), outputBuffer.Length());
} else {
for (BHttpForm::Iterator it = fOptPostFields->GetIterator();
const BHttpFormData* currentField = it.Next();
) {
_EmitDebug(B_URL_PROTOCOL_DEBUG_TRANSFER_OUT,
it.MultipartHeader().String());
fSocket->Write(it.MultipartHeader().String(),
it.MultipartHeader().Length());
switch (currentField->Type()) {
default:
case B_HTTPFORM_UNKNOWN:
ASSERT(0);
break;
case B_HTTPFORM_STRING:
fSocket->Write(currentField->String().String(),
currentField->String().Length());
break;
case B_HTTPFORM_FILE:
{
BFile upFile(currentField->File().Path(),
B_READ_ONLY);
char readBuffer[kHttpBufferSize];
ssize_t readSize;
readSize = upFile.Read(readBuffer,
sizeof(readBuffer));
while (readSize > 0) {
fSocket->Write(readBuffer, readSize);
readSize = upFile.Read(readBuffer,
sizeof(readBuffer));
}
break;
}
case B_HTTPFORM_BUFFER:
fSocket->Write(currentField->Buffer(),
currentField->BufferSize());
break;
}
fSocket->Write("\r\n", 2);
}
BString footer = fOptPostFields->GetMultipartFooter();
fSocket->Write(footer.String(), footer.Length());
}
} else if ((fRequestMethod == B_HTTP_POST || fRequestMethod == B_HTTP_PUT)
&& fOptInputData != NULL) {
for (;;) {
char outputTempBuffer[kHttpBufferSize];
ssize_t read = fOptInputData->Read(outputTempBuffer,
sizeof(outputTempBuffer));
if (read <= 0)
break;
if (fOptInputDataSize < 0) {
// Chunked transfer
char hexSize[16];
size_t hexLength = sprintf(hexSize, "%ld", read);
fSocket->Write(hexSize, hexLength);
fSocket->Write("\r\n", 2);
fSocket->Write(outputTempBuffer, read);
fSocket->Write("\r\n", 2);
} else {
fSocket->Write(outputTempBuffer, read);
}
}
if (fOptInputDataSize < 0) {
// Chunked transfer terminating sequence
fSocket->Write("0\r\n\r\n", 5);
}
}
fRequestStatus = kRequestInitialState;
// Receive loop
bool receiveEnd = false;
bool parseEnd = false;
bool readByChunks = false;
status_t readError = B_OK;
ssize_t bytesRead = 0;
ssize_t bytesReceived = 0;
ssize_t bytesTotal = 0;
char* inputTempBuffer = new char[kHttpBufferSize];
ssize_t inputTempSize = kHttpBufferSize;
ssize_t chunkSize = -1;
while (!fQuit && !(receiveEnd && parseEnd)) {
if (!receiveEnd) {
fSocket->WaitForReadable();
BNetBuffer chunk(kHttpBufferSize);
bytesRead = fSocket->Read(chunk.Data(), kHttpBufferSize);
if (bytesRead < 0) {
readError = bytesRead;
break;
} else if (bytesRead == 0)
receiveEnd = true;
fInputBuffer.AppendData(chunk.Data(), bytesRead);
} else
bytesRead = 0;
if (fRequestStatus < kRequestStatusReceived) {
_ParseStatus();
//! ProtocolHook:ResponseStarted
if (fRequestStatus >= kRequestStatusReceived && fListener != NULL)
fListener->ResponseStarted(this);
}
if (fRequestStatus < kRequestHeadersReceived) {
_ParseHeaders();
if (fRequestStatus >= kRequestHeadersReceived) {
_ResultHeaders() = fHeaders;
//! ProtocolHook:HeadersReceived
if (fListener != NULL)
fListener->HeadersReceived(this);
// Parse received cookies
if (fContext != NULL) {
for (int32 i = 0; i < fHeaders.CountHeaders(); i++) {
if (fHeaders.HeaderAt(i).NameIs("Set-Cookie")) {
fContext->GetCookieJar().AddCookie(
fHeaders.HeaderAt(i).Value(), fUrl);
}
}
}
if (BString(fHeaders["Transfer-Encoding"]) == "chunked")
readByChunks = true;
int32 index = fHeaders.HasHeader("Content-Length");
if (index != B_ERROR)
bytesTotal = atoi(fHeaders.HeaderAt(index).Value());
else
bytesTotal = 0;
}
}
if (fRequestStatus >= kRequestHeadersReceived) {
// If Transfer-Encoding is chunked, we should read a complete
// chunk in buffer before handling it
if (readByChunks) {
if (chunkSize >= 0) {
if ((ssize_t)fInputBuffer.Size() >= chunkSize + 2) {
// 2 more bytes to handle the closing CR+LF
bytesRead = chunkSize;
if (inputTempSize < chunkSize + 2) {
delete[] inputTempBuffer;
inputTempSize = chunkSize + 2;
inputTempBuffer = new char[inputTempSize];
}
fInputBuffer.RemoveData(inputTempBuffer,
chunkSize + 2);
chunkSize = -1;
} else {
// Not enough data, try again later
bytesRead = -1;
}
} else {
BString chunkHeader;
if (_GetLine(chunkHeader) == B_ERROR) {
chunkSize = -1;
bytesRead = -1;
} else {
// Format of a chunk header:
// <chunk size in hex>[; optional data]
int32 semiColonIndex = chunkHeader.FindFirst(";", 0);
// Cut-off optional data if present
if (semiColonIndex != -1) {
chunkHeader.Remove(semiColonIndex,
chunkHeader.Length() - semiColonIndex);
}
chunkSize = strtol(chunkHeader.String(), NULL, 16);
PRINT(("BHP[%p] Chunk %s=%ld\n", this,
chunkHeader.String(), chunkSize));
if (chunkSize == 0)
fRequestStatus = kRequestContentReceived;
bytesRead = -1;
}
}
// A chunk of 0 bytes indicates the end of the chunked
// transfer
if (bytesRead == 0)
receiveEnd = true;
} else {
bytesRead = fInputBuffer.Size();
if (bytesRead > 0) {
if (inputTempSize < bytesRead) {
inputTempSize = bytesRead;
delete[] inputTempBuffer;
inputTempBuffer = new char[bytesRead];
}
fInputBuffer.RemoveData(inputTempBuffer, bytesRead);
}
}
if (bytesRead > 0) {
bytesReceived += bytesRead;
if (fListener != NULL) {
fListener->DataReceived(this, inputTempBuffer, bytesRead);
fListener->DownloadProgress(this, bytesReceived,
bytesTotal);
}
if (bytesTotal > 0 && bytesReceived >= bytesTotal)
receiveEnd = true;
}
}
parseEnd = (fInputBuffer.Size() == 0);
}
fSocket->Disconnect();
delete[] inputTempBuffer;
if (readError != B_OK)
return readError;
return fQuit ? B_INTERRUPTED : B_OK;
}
status_t
BHttpRequest::_GetLine(BString& destString)
{
// Find a complete line in inputBuffer
uint32 characterIndex = 0;
while ((characterIndex < fInputBuffer.Size())
&& ((fInputBuffer.Data())[characterIndex] != '\n'))
characterIndex++;
if (characterIndex == fInputBuffer.Size())
return B_ERROR;
char* temporaryBuffer = new(std::nothrow) char[characterIndex + 1];
fInputBuffer.RemoveData(temporaryBuffer, characterIndex + 1);
// Strip end-of-line character(s)
if (temporaryBuffer[characterIndex - 1] == '\r')
destString.SetTo(temporaryBuffer, characterIndex - 1);
else
destString.SetTo(temporaryBuffer, characterIndex);
delete[] temporaryBuffer;
return B_OK;
}
void
BHttpRequest::_ParseStatus()
{
// Status line should be formatted like: HTTP/M.m SSS ...
// With: M = Major version of the protocol
// m = Minor version of the protocol
// SSS = three-digit status code of the response
// ... = additional text info
BString statusLine;
if (_GetLine(statusLine) == B_ERROR)
return;
if (statusLine.CountChars() < 12)
return;
fRequestStatus = kRequestStatusReceived;
BString statusCodeStr;
BString statusText;
statusLine.CopyInto(statusCodeStr, 9, 3);
_SetResultStatusCode(atoi(statusCodeStr.String()));
statusLine.CopyInto(_ResultStatusText(), 13, statusLine.Length() - 13);
_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Status line received: Code %d (%s)",
atoi(statusCodeStr.String()), _ResultStatusText().String());
}
void
BHttpRequest::_ParseHeaders()
{
BString currentHeader;
if (_GetLine(currentHeader) == B_ERROR)
return;
// An empty line means the end of the header section
if (currentHeader.Length() == 0) {
fRequestStatus = kRequestHeadersReceived;
return;
}
_EmitDebug(B_URL_PROTOCOL_DEBUG_HEADER_IN, "%s", currentHeader.String());
fHeaders.AddHeader(currentHeader.String());
}
void
BHttpRequest::_SendRequest()
{
BString request(fRequestMethod);
if (Url().HasPath())
request << ' ' << Url().Path();
else
request << " /";
if (Url().HasRequest())
request << '?' << Url().Request();
switch (fHttpVersion) {
case B_HTTP_11:
request << " HTTP/1.1\r\n";
break;
default:
case B_HTTP_10:
request << " HTTP/1.0\r\n";
break;
}
fSocket->Write(request.String(), request.Length());
}
void
BHttpRequest::_SendHeaders()
{
// HTTP 1.1 additional headers
if (fHttpVersion == B_HTTP_11) {
fOutputHeaders.AddHeader("Host", Url().Host());
fOutputHeaders.AddHeader("Accept", "*/*");
fOutputHeaders.AddHeader("Accept-Encoding", "chunked");
// Allow the remote server to send dynamic content by chunks
// rather than waiting for the full content to be generated and
// sending us data.
fOutputHeaders.AddHeader("Connection", "close");
// Let the remote server close the connection after response since
// we don't handle multiple request on a single connection
}
// Classic HTTP headers
if (fOptUserAgent.CountChars() > 0)
fOutputHeaders.AddHeader("User-Agent", fOptUserAgent.String());
if (fOptReferer.CountChars() > 0)
fOutputHeaders.AddHeader("Referer", fOptReferer.String());
// Authentication
BHttpAuthentication& authentication = fContext->GetAuthentication(fUrl);
if (authentication.Method() != B_HTTP_AUTHENTICATION_NONE) {
if (fOptUsername.Length() > 0) {
authentication.SetUserName(fOptUsername);
authentication.SetPassword(fOptPassword);
}
BString request(fRequestMethod);
fOutputHeaders.AddHeader("Authorization",
authentication.Authorization(fUrl, request));
}
// Required headers for POST data
if (fOptPostFields != NULL && fRequestMethod == B_HTTP_POST) {
BString contentType;
switch (fOptPostFields->GetFormType()) {
case B_HTTP_FORM_MULTIPART:
contentType << "multipart/form-data; boundary="
<< fOptPostFields->GetMultipartBoundary() << "";
break;
case B_HTTP_FORM_URL_ENCODED:
contentType << "application/x-www-form-urlencoded";
break;
}
fOutputHeaders.AddHeader("Content-Type", contentType);
fOutputHeaders.AddHeader("Content-Length",
fOptPostFields->ContentLength());
} else if (fOptInputData != NULL
&& (fRequestMethod == B_HTTP_POST || fRequestMethod == B_HTTP_PUT)) {
if (fOptInputDataSize >= 0)
fOutputHeaders.AddHeader("Content-Length", fOptInputDataSize);
else
fOutputHeaders.AddHeader("Transfer-Encoding", "chunked");
}
// Optional headers specified by the user
if (fOptHeaders != NULL) {
for (int32 headerIndex = 0; headerIndex < fOptHeaders->CountHeaders();
headerIndex++) {
BHttpHeader& optHeader = (*fOptHeaders)[headerIndex];
int32 replaceIndex = fOutputHeaders.HasHeader(optHeader.Name());
// Add or replace the current option header to the
// output header list
if (replaceIndex == -1)
fOutputHeaders.AddHeader(optHeader.Name(), optHeader.Value());
else
fOutputHeaders[replaceIndex].SetValue(optHeader.Value());
}
}
// Context cookies
if (fOptSetCookies && (fContext != NULL)) {
BNetworkCookie* cookie;
BString cookieString;
for (BNetworkCookieJar::UrlIterator it
= fContext->GetCookieJar().GetUrlIterator(fUrl);
(cookie = it.Next()) != NULL;) {
cookieString << cookie->RawCookie(false);
cookieString << "; ";
}
// Remove the extra "; " we added after the last item.
cookieString.Truncate(cookieString.Length() - 2);
if (cookieString.Length() > 0)
fOutputHeaders.AddHeader("Cookie", cookieString);
}
// Write output headers to output stream
for (int32 headerIndex = 0; headerIndex < fOutputHeaders.CountHeaders();
headerIndex++) {
const char* header = fOutputHeaders.HeaderAt(headerIndex).Header();
fSocket->Write(header, strlen(header));
fSocket->Write("\r\n", 2);
_EmitDebug(B_URL_PROTOCOL_DEBUG_HEADER_OUT, "%s", header);
}
}
BHttpHeaders&
BHttpRequest::_ResultHeaders()
{
return fResult.fHeaders;
}
void
BHttpRequest::_SetResultStatusCode(int32 statusCode)
{
fResult.fStatusCode = statusCode;
}
BString&
BHttpRequest::_ResultStatusText()
{
return fResult.fStatusString;
}