// ----------------------------------------------------------------------
// File: HttpServer.cc
// Author: Andreas-Joachim Peters & Justin Lewis Salmon - CERN
// ----------------------------------------------------------------------
/************************************************************************
* EOS - the CERN Disk Storage System *
* Copyright (C) 2011 CERN/Switzerland *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see .*
************************************************************************/
#include "fst/http/HttpServer.hh"
#include "fst/http/ProtocolHandlerFactory.hh"
#include "fst/XrdFstOfsFile.hh"
#include "common/http/ProtocolHandler.hh"
#include "common/SecEntity.hh"
#include "fst/XrdFstOfs.hh"
#include "XrdSys/XrdSysPthread.hh"
#include "XrdSfs/XrdSfsInterface.hh"
EOSFSTNAMESPACE_BEGIN
#ifdef EOS_MICRO_HTTPD
/*----------------------------------------------------------------------------*/
int
HttpServer::Handler(void* cls,
struct MHD_Connection* connection,
const char* url,
const char* method,
const char* version,
const char* uploadData,
size_t* uploadDataSize,
void** ptr)
{
// The handler function is called in a 'stateless' fashion, so to keep state
// the implementation stores a HttpHandler object using **ptr.
// libmicrohttpd moreover deals with 100-continue responses used by PUT/POST
// in the upper protocol level, so the handler has to return for GET requests
// just MHD_YES if there is not yet an HttpHandler and for PUT requests
// should only create a response object if the open for the PUT fails for
// whatever reason.
// So when the HTTP header have arrived Handler is called the first time
// and in following Handler calls we should not decode the headers again and
// again for performance reasons. So there is a difference between handling of
// GET and PUT because in GET we just don't do anything but return and decode
// the HTTP headers with the second call, while for PUT we do it in the first
// call and open the output file immediately to return evt. an error.
std::map headers;
// If this is the first call, create an appropriate protocol handler based
// on the headers and store it in *ptr. We should only return MHD_YES here
// (unless error)
if (*ptr == 0) {
// Get the headers
MHD_get_connection_values(connection, MHD_HEADER_KIND,
&HttpServer::BuildHeaderMap, (void*) &headers);
eos::common::ProtocolHandler* handler;
ProtocolHandlerFactory factory = ProtocolHandlerFactory();
handler = factory.CreateProtocolHandler(method, headers, 0);
if (!handler) {
eos_static_err("msg=No matching protocol for request");
return MHD_NO;
}
*ptr = handler;
return MHD_YES;
}
// Retrieve the protocol handler stored in *ptr
eos::common::ProtocolHandler* protocolHandler = (eos::common::ProtocolHandler*)
* ptr;
// For requests which have a body (i.e. uploadDataSize != 0) we must handle
// the body data on the second reentrant call to this function. We must
// create the response and store it inside the protocol handler, but we must
// NOT queue the response until the third call.
if (!protocolHandler->GetResponse() ||
!protocolHandler->GetResponse()->GetResponseCode()) {
// Get the request headers again
MHD_get_connection_values(connection, MHD_HEADER_KIND,
&HttpServer::BuildHeaderMap, (void*) &headers);
// Get the request query string
std::string query;
MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND,
&HttpServer::BuildQueryString, (void*) &query);
// Get the cookies
std::map cookies;
MHD_get_connection_values(connection, MHD_COOKIE_KIND,
&HttpServer::BuildHeaderMap, (void*) &cookies);
// Make a request object
std::string body(uploadData, *uploadDataSize);
eos::common::HttpRequest* request = new eos::common::HttpRequest(
headers, method, url,
query.c_str() ? query : "",
body, uploadDataSize, cookies);
eos_static_debug("\n\n%s", request->ToString().c_str());
// Handle the request and build a response based on the specific protocol
protocolHandler->HandleRequest(request);
delete request;
}
eos::common::HttpResponse* response = protocolHandler->GetResponse();
if (!response) {
eos_static_crit("msg=\"response creation failed\"");
return MHD_NO;
}
if (*uploadDataSize != 0) {
eos_static_debug("returning MHD_NO response-code=%d to stop upload",
response->GetResponseCode());
if (response->GetResponseCode()) {
eos_static_debug("setting uploadDataSize to 0");
*uploadDataSize = 0;
if (response->GetResponseCode() >= 300) {
eos_static_debug("failing request with response code %d",
response->GetResponseCode());
protocolHandler->DeleteResponse();
return MHD_NO;
}
}
protocolHandler->DeleteResponse();
return MHD_YES;
}
eos_static_debug("\n\n%s", response->ToString().c_str());
// Create the MHD response
struct MHD_Response* mhdResponse;
if (response->mUseFileReaderCallback) {
eos_static_debug("response length=%d", response->mResponseLength);
mhdResponse = MHD_create_response_from_callback(response->mResponseLength,
4 * 1024 * 1024, /* 4M page size */
&HttpServer::FileReaderCallback,
(void*) protocolHandler, 0);
} else {
mhdResponse = MHD_create_response_from_buffer(response->GetBodySize(),
(void*) response->GetBody().c_str(),
MHD_RESPMEM_PERSISTENT);
}
if (mhdResponse) {
// Add all the response header tags
headers = response->GetHeaders();
for (auto it = headers.begin(); it != headers.end(); it++) {
MHD_add_response_header(mhdResponse, it->first.c_str(), it->second.c_str());
}
// Queue the response
int ret = MHD_queue_response(connection, response->GetResponseCode(),
mhdResponse);
eos_static_debug("MHD_queue_response ret=%d", ret);
MHD_destroy_response(mhdResponse);
return ret;
} else {
eos_static_crit("msg=\"response creation failed\"");
return MHD_NO;
}
}
void
HttpServer::CompleteHandler(void* cls,
struct MHD_Connection* connection,
void** con_cls,
enum MHD_RequestTerminationCode toe)
{
std::string scode = "";
if (toe == MHD_REQUEST_TERMINATED_COMPLETED_OK) {
scode = "OK";
}
if (toe == MHD_REQUEST_TERMINATED_WITH_ERROR) {
scode = "Error";
}
if (toe == MHD_REQUEST_TERMINATED_TIMEOUT_REACHED) {
scode = "Timeout";
}
if (toe == MHD_REQUEST_TERMINATED_DAEMON_SHUTDOWN) {
scode = "Shutdown";
}
if (toe == MHD_REQUEST_TERMINATED_READ_ERROR) {
scode = "ReadError";
}
eos_static_info("msg=\"http connection disconnect\" reason=\"Request %s\" ",
scode.c_str());
eos::fst::HttpHandler* httpHandle = 0;
if ((con_cls && (*con_cls))) {
eos::common::ProtocolHandler* handler =
static_cast(*con_cls);
httpHandle = dynamic_cast(handler);
}
if (httpHandle) {
// deal with delete-on-close logic
if ((toe != MHD_REQUEST_TERMINATED_COMPLETED_OK)) {
eos_static_info("msg=\"http connection disconnect\" action=\"Cleanup\" ");
if (httpHandle && httpHandle->mFile) {
eos_static_err("msg=\"clean-up interrupted PUT/GET request\" path=\"%s\"",
httpHandle->mFile->GetPath().c_str());
// we have to disable delete-on-close for chunked uploads since files are stateful
if (httpHandle->mFile->IsChunkedUpload()) {
httpHandle->mFile->close();
}
}
}
// clean-up file objects
if (httpHandle->mFile) {
delete(httpHandle->mFile);
httpHandle->mFile = 0;
}
delete httpHandle;
*con_cls = 0;
}
}
#endif
/*----------------------------------------------------------------------------*/
ssize_t
HttpServer::FileReader(eos::common::ProtocolHandler* handler, uint64_t pos,
char* buf, size_t max)
{
return HttpServer::FileReaderCallback(handler, pos, buf, max);
}
/*----------------------------------------------------------------------------*/
ssize_t
HttpServer::FileWriter(eos::common::ProtocolHandler* handler,
std::string& method,
std::string& uri,
std::map& headers,
std::string& query,
std::map& cookies,
std::string& body)
{
eos::fst::HttpHandler* httpHandle = dynamic_cast
(handler);
size_t uploadSize = body.size();
std::unique_ptr request(new eos::common::HttpRequest(
headers, method, uri,
query.c_str(),
body, &uploadSize, cookies,
true));
eos_static_debug("\n\n%s", request->ToString().c_str());
// Handle the request and build a response based on the specific protocol
httpHandle->HandleRequest(request.get());
eos::common::HttpResponse* response = handler->GetResponse();
if (response->GetResponseCode() == response->CREATED) {
return 0;
} else {
return -1;
}
}
/*----------------------------------------------------------------------------*/
ssize_t
HttpServer::FileClose(eos::common::ProtocolHandler* handler, int rc)
{
eos::fst::HttpHandler* httpHandle = dynamic_cast
(handler);
if (httpHandle && httpHandle->mFile) {
if (rc) {
eos_static_err("msg=\"clean-up interrupted or IO error related PUT/GET request\" path=\"%s\"",
httpHandle->mFile->GetPath().c_str());
// we have to disable delete-on-close for chunked uploads since files are stateful
if (httpHandle->mFile->IsChunkedUpload()) {
httpHandle->mFile->close();
}
} else {
httpHandle->mFile->close();
}
// clean-up file objects
if (httpHandle->mFile) {
delete(httpHandle->mFile);
httpHandle->mFile = 0;
}
}
return 0;
}
/*----------------------------------------------------------------------------*/
ssize_t
HttpServer::FileReaderCallback(void* cls, uint64_t pos, char* buf, size_t max)
{
// Ugly ugly casting hack
eos::common::ProtocolHandler* handler =
static_cast(cls);
eos::fst::HttpHandler* httpHandle = dynamic_cast
(handler);
if (!httpHandle) {
eos_static_err("error: dynamic cast to eos::fst::HttpHandler failed");
return -1;
}
eos_static_debug("pos=%llu max=%llu current-index=%d current-offset=%llu",
(unsigned long long) pos,
(unsigned long long) max,
httpHandle->mCurrentCallbackOffsetIndex,
httpHandle->mCurrentCallbackOffset);
size_t readsofar = 0;
if (httpHandle && httpHandle->mFile) {
if (httpHandle->mRangeRequest) {
// range request
if (httpHandle->mCurrentCallbackOffsetIndex < httpHandle->mOffsetMap.size()) {
size_t toread = 0;
// if the currentoffset is 0 we have to place the multipart header first
if ((httpHandle->mOffsetMap.size() > 1) &&
(httpHandle->mCurrentCallbackOffset == 0)) {
eos_static_debug("place=%s", httpHandle->mMultipartHeaderMap
[httpHandle->mCurrentCallbackOffsetIndex].c_str());
toread = httpHandle->mMultipartHeaderMap
[httpHandle->mCurrentCallbackOffsetIndex].length();
// this is the start of a range request, copy the multipart header
memcpy(buf,
httpHandle->mMultipartHeaderMap
[httpHandle->mCurrentCallbackOffsetIndex].c_str(),
toread
);
readsofar += toread;
}
auto it = httpHandle->mOffsetMap.begin();
// advance to the index position
std::advance(it, httpHandle->mCurrentCallbackOffsetIndex);
int nread = 0;
// now read from offset
do {
off_t offset = it->first;
off_t indexoffset = httpHandle->mCurrentCallbackOffset;
toread = max - readsofar;
// see how much we can still read from this offsetmap
if (toread > (size_t)(it->second - indexoffset)) {
toread = (it->second - indexoffset);
}
eos_static_debug("toread=%llu", (unsigned long long) toread);
// read the block
nread = httpHandle->mFile->read(offset + indexoffset,
buf + readsofar, toread);
// there is a read error here!
if (toread && (nread != (int) toread)) {
return -1;
}
if (nread > 0) {
readsofar += nread;
}
if ((it->second - indexoffset) == nread) {
eos_static_debug("leaving");
// we have to move to the next index;
it++;
httpHandle->mCurrentCallbackOffsetIndex++;
httpHandle->mCurrentCallbackOffset = 0;
// we stop the loop
break;
} else {
if (nread > 0) {
httpHandle->mCurrentCallbackOffset += nread;
}
eos_static_debug("callback-offset(now)=%llu", (unsigned long long)
httpHandle->mCurrentCallbackOffset);
}
} while ((nread > 0) &&
(readsofar < max) &&
(it != httpHandle->mOffsetMap.end()));
eos_static_debug("read=%llu", (unsigned long long) readsofar);
return readsofar;
} else {
if (httpHandle->mOffsetMap.size() > 1) {
if (httpHandle->mBoundaryEndSent) {
// we are done here
return 0;
} else {
httpHandle->mBoundaryEndSent = true;
memcpy(buf, httpHandle->mBoundaryEnd.c_str(),
httpHandle->mBoundaryEnd.length());
eos_static_debug("read=%llu [boundary-end]", (unsigned long long)
httpHandle->mBoundaryEnd.length());
return httpHandle->mBoundaryEnd.length();
}
} else {
return 0;
}
}
} else {
// file streaming
if (max) {
size_t nread = httpHandle->mFile->read(pos, buf, max);
if (nread == 0) {
return -1;
} else {
return nread;
}
} else {
return -1;
}
}
}
return 0;
}
std::unique_ptr
HttpServer::XrdHttpHandler(std::string& method,
std::string& uri,
std::map& headers,
std::string& query,
std::map& cookies,
std::string& body,
const XrdSecEntity& client)
{
if (client.moninfo && strlen(client.moninfo)) {
headers["ssl_client_s_dn"] = client.moninfo;
headers["x-real-ip"] = client.host;
}
ProtocolHandlerFactory factory = ProtocolHandlerFactory();
std::unique_ptr handler(
factory.CreateProtocolHandler(method, headers, 0));
if (!handler) {
eos_static_err("msg=\"no matching protocol for request method %s\"",
method.c_str());
return 0;
}
size_t bodySize = body.length();
// Retrieve the protocol handler stored in *ptr
std::unique_ptr request(new eos::common::HttpRequest(
headers, method, uri,
query.c_str() ? query : "",
body, &bodySize, cookies, true));
if (EOS_LOGS_DEBUG) {
eos_static_debug("\n\n%s\n%s\n", request->ToString().c_str(),
request->GetBody().c_str());
}
handler->HandleRequest(request.get());
if (EOS_LOGS_DEBUG) {
eos_static_debug("method=%s uri='%s' %s (warning this is not the mapped identity)",
method.c_str(), uri.c_str(), eos::common::SecEntity::ToString(&client,
"xrdhttp").c_str());
}
return handler;
}
EOSFSTNAMESPACE_END