replace happyhttp with cpp-httplib
This commit is contained in:
parent
fba8417ddf
commit
4a2adff0c8
11
external/CMakeLists.txt
vendored
11
external/CMakeLists.txt
vendored
@ -2,8 +2,6 @@ cmake_minimum_required(VERSION 3.14...3.24 FATAL_ERROR)
|
|||||||
|
|
||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
|
|
||||||
add_subdirectory(./happyhttp)
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
NOT TARGET libsodium AND
|
NOT TARGET libsodium AND
|
||||||
NOT TARGET unofficial-sodium::sodium AND
|
NOT TARGET unofficial-sodium::sodium AND
|
||||||
@ -77,6 +75,15 @@ if (NOT TARGET solanaceae_plugin)
|
|||||||
FetchContent_MakeAvailable(solanaceae_plugin)
|
FetchContent_MakeAvailable(solanaceae_plugin)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (NOT TARGET httplib::httplib)
|
||||||
|
FetchContent_Declare(httplib
|
||||||
|
GIT_REPOSITORY https://github.com/yhirose/cpp-httplib.git
|
||||||
|
GIT_TAG v0.15.3
|
||||||
|
EXCLUDE_FROM_ALL
|
||||||
|
)
|
||||||
|
FetchContent_MakeAvailable(httplib)
|
||||||
|
endif()
|
||||||
|
|
||||||
if (NOT TARGET nlohmann_json::nlohmann_json)
|
if (NOT TARGET nlohmann_json::nlohmann_json)
|
||||||
FetchContent_Declare(json
|
FetchContent_Declare(json
|
||||||
URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz
|
URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz
|
||||||
|
13
external/happyhttp/CMakeLists.txt
vendored
13
external/happyhttp/CMakeLists.txt
vendored
@ -1,13 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
|
|
||||||
|
|
||||||
add_library(happyhttp STATIC
|
|
||||||
./happyhttp/happyhttp.h
|
|
||||||
./happyhttp/happyhttp.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
target_include_directories(happyhttp PUBLIC .)
|
|
||||||
target_compile_features(happyhttp PUBLIC cxx_std_11)
|
|
||||||
if(WIN32)
|
|
||||||
target_link_libraries(happyhttp iphlpapi ws2_32)
|
|
||||||
endif()
|
|
||||||
|
|
940
external/happyhttp/happyhttp/happyhttp.cpp
vendored
940
external/happyhttp/happyhttp/happyhttp.cpp
vendored
@ -1,940 +0,0 @@
|
|||||||
/*
|
|
||||||
* HappyHTTP - a simple HTTP library
|
|
||||||
* Version 0.1
|
|
||||||
*
|
|
||||||
* Copyright (c) 2006 Ben Campbell
|
|
||||||
*
|
|
||||||
* This software is provided 'as-is', without any express or implied
|
|
||||||
* warranty. In no event will the authors be held liable for any damages
|
|
||||||
* arising from the use of this software.
|
|
||||||
*
|
|
||||||
* Permission is granted to anyone to use this software for any purpose,
|
|
||||||
* including commercial applications, and to alter it and redistribute it
|
|
||||||
* freely, subject to the following restrictions:
|
|
||||||
*
|
|
||||||
* 1. The origin of this software must not be misrepresented; you must not
|
|
||||||
* claim that you wrote the original software. If you use this software in a
|
|
||||||
* product, an acknowledgment in the product documentation would be
|
|
||||||
* appreciated but is not required.
|
|
||||||
*
|
|
||||||
* 2. Altered source versions must be plainly marked as such, and must not
|
|
||||||
* be misrepresented as being the original software.
|
|
||||||
*
|
|
||||||
* 3. This notice may not be removed or altered from any source distribution.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#include "happyhttp.h"
|
|
||||||
|
|
||||||
#ifndef _WIN32
|
|
||||||
// #include <sys/types.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <netinet/in.h>
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
#include <netdb.h> // for gethostbyname()
|
|
||||||
#include <errno.h>
|
|
||||||
#else
|
|
||||||
#include <WinSock2.h>
|
|
||||||
#define vsnprintf _vsnprintf
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cstring>
|
|
||||||
#include <cstdarg>
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#ifndef _WIN32
|
|
||||||
#include <strings.h>
|
|
||||||
#define _stricmp strcasecmp
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
|
|
||||||
namespace happyhttp
|
|
||||||
{
|
|
||||||
|
|
||||||
#ifdef WIN32
|
|
||||||
const char* GetWinsockErrorString( int err );
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
//---------------------------------------------------------------------
|
|
||||||
// Helper functions
|
|
||||||
//---------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void BailOnSocketError( const char* context )
|
|
||||||
{
|
|
||||||
#ifdef WIN32
|
|
||||||
|
|
||||||
int e = WSAGetLastError();
|
|
||||||
const char* msg = GetWinsockErrorString( e );
|
|
||||||
#else
|
|
||||||
const char* msg = strerror( errno );
|
|
||||||
#endif
|
|
||||||
throw Wobbly( "%s: %s", context, msg );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef WIN32
|
|
||||||
|
|
||||||
const char* GetWinsockErrorString( int err )
|
|
||||||
{
|
|
||||||
switch( err)
|
|
||||||
{
|
|
||||||
case 0: return "No error";
|
|
||||||
case WSAEINTR: return "Interrupted system call";
|
|
||||||
case WSAEBADF: return "Bad file number";
|
|
||||||
case WSAEACCES: return "Permission denied";
|
|
||||||
case WSAEFAULT: return "Bad address";
|
|
||||||
case WSAEINVAL: return "Invalid argument";
|
|
||||||
case WSAEMFILE: return "Too many open sockets";
|
|
||||||
case WSAEWOULDBLOCK: return "Operation would block";
|
|
||||||
case WSAEINPROGRESS: return "Operation now in progress";
|
|
||||||
case WSAEALREADY: return "Operation already in progress";
|
|
||||||
case WSAENOTSOCK: return "Socket operation on non-socket";
|
|
||||||
case WSAEDESTADDRREQ: return "Destination address required";
|
|
||||||
case WSAEMSGSIZE: return "Message too long";
|
|
||||||
case WSAEPROTOTYPE: return "Protocol wrong type for socket";
|
|
||||||
case WSAENOPROTOOPT: return "Bad protocol option";
|
|
||||||
case WSAEPROTONOSUPPORT: return "Protocol not supported";
|
|
||||||
case WSAESOCKTNOSUPPORT: return "Socket type not supported";
|
|
||||||
case WSAEOPNOTSUPP: return "Operation not supported on socket";
|
|
||||||
case WSAEPFNOSUPPORT: return "Protocol family not supported";
|
|
||||||
case WSAEAFNOSUPPORT: return "Address family not supported";
|
|
||||||
case WSAEADDRINUSE: return "Address already in use";
|
|
||||||
case WSAEADDRNOTAVAIL: return "Can't assign requested address";
|
|
||||||
case WSAENETDOWN: return "Network is down";
|
|
||||||
case WSAENETUNREACH: return "Network is unreachable";
|
|
||||||
case WSAENETRESET: return "Net connection reset";
|
|
||||||
case WSAECONNABORTED: return "Software caused connection abort";
|
|
||||||
case WSAECONNRESET: return "Connection reset by peer";
|
|
||||||
case WSAENOBUFS: return "No buffer space available";
|
|
||||||
case WSAEISCONN: return "Socket is already connected";
|
|
||||||
case WSAENOTCONN: return "Socket is not connected";
|
|
||||||
case WSAESHUTDOWN: return "Can't send after socket shutdown";
|
|
||||||
case WSAETOOMANYREFS: return "Too many references, can't splice";
|
|
||||||
case WSAETIMEDOUT: return "Connection timed out";
|
|
||||||
case WSAECONNREFUSED: return "Connection refused";
|
|
||||||
case WSAELOOP: return "Too many levels of symbolic links";
|
|
||||||
case WSAENAMETOOLONG: return "File name too long";
|
|
||||||
case WSAEHOSTDOWN: return "Host is down";
|
|
||||||
case WSAEHOSTUNREACH: return "No route to host";
|
|
||||||
case WSAENOTEMPTY: return "Directory not empty";
|
|
||||||
case WSAEPROCLIM: return "Too many processes";
|
|
||||||
case WSAEUSERS: return "Too many users";
|
|
||||||
case WSAEDQUOT: return "Disc quota exceeded";
|
|
||||||
case WSAESTALE: return "Stale NFS file handle";
|
|
||||||
case WSAEREMOTE: return "Too many levels of remote in path";
|
|
||||||
case WSASYSNOTREADY: return "Network system is unavailable";
|
|
||||||
case WSAVERNOTSUPPORTED: return "Winsock version out of range";
|
|
||||||
case WSANOTINITIALISED: return "WSAStartup not yet called";
|
|
||||||
case WSAEDISCON: return "Graceful shutdown in progress";
|
|
||||||
case WSAHOST_NOT_FOUND: return "Host not found";
|
|
||||||
case WSANO_DATA: return "No host data of that type was found";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "unknown";
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // WIN32
|
|
||||||
|
|
||||||
|
|
||||||
// return true if socket has data waiting to be read
|
|
||||||
bool datawaiting( int sock )
|
|
||||||
{
|
|
||||||
fd_set fds;
|
|
||||||
FD_ZERO( &fds );
|
|
||||||
FD_SET( sock, &fds );
|
|
||||||
|
|
||||||
struct timeval tv;
|
|
||||||
tv.tv_sec = 0;
|
|
||||||
tv.tv_usec = 0;
|
|
||||||
|
|
||||||
int r = select( sock+1, &fds, NULL, NULL, &tv);
|
|
||||||
if (r < 0)
|
|
||||||
BailOnSocketError( "select" );
|
|
||||||
|
|
||||||
if( FD_ISSET( sock, &fds ) )
|
|
||||||
return true;
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Try to work out address from string
|
|
||||||
// returns 0 if bad
|
|
||||||
struct in_addr *atoaddr( const char* address)
|
|
||||||
{
|
|
||||||
struct hostent *host;
|
|
||||||
static struct in_addr saddr;
|
|
||||||
|
|
||||||
// First try nnn.nnn.nnn.nnn form
|
|
||||||
saddr.s_addr = inet_addr(address);
|
|
||||||
if (saddr.s_addr != -1)
|
|
||||||
return &saddr;
|
|
||||||
|
|
||||||
host = gethostbyname(address);
|
|
||||||
if( host )
|
|
||||||
return (struct in_addr *) *host->h_addr_list;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//---------------------------------------------------------------------
|
|
||||||
//
|
|
||||||
// Exception class
|
|
||||||
//
|
|
||||||
//---------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
Wobbly::Wobbly( const char* fmt, ... )
|
|
||||||
{
|
|
||||||
va_list ap;
|
|
||||||
va_start( ap,fmt);
|
|
||||||
int n = vsnprintf( m_Message, MAXLEN, fmt, ap );
|
|
||||||
va_end( ap );
|
|
||||||
if(n==MAXLEN)
|
|
||||||
m_Message[MAXLEN-1] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//---------------------------------------------------------------------
|
|
||||||
//
|
|
||||||
// Connection
|
|
||||||
//
|
|
||||||
//---------------------------------------------------------------------
|
|
||||||
Connection::Connection( const char* host, int port ) :
|
|
||||||
m_ResponseBeginCB(0),
|
|
||||||
m_ResponseDataCB(0),
|
|
||||||
m_ResponseCompleteCB(0),
|
|
||||||
m_UserData(0),
|
|
||||||
m_State( IDLE ),
|
|
||||||
m_Host( host ),
|
|
||||||
m_Port( port ),
|
|
||||||
m_Sock(-1)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Connection::setcallbacks(
|
|
||||||
ResponseBegin_CB begincb,
|
|
||||||
ResponseData_CB datacb,
|
|
||||||
ResponseComplete_CB completecb,
|
|
||||||
void* userdata )
|
|
||||||
{
|
|
||||||
m_ResponseBeginCB = begincb;
|
|
||||||
m_ResponseDataCB = datacb;
|
|
||||||
m_ResponseCompleteCB = completecb;
|
|
||||||
m_UserData = userdata;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Connection::connect()
|
|
||||||
{
|
|
||||||
in_addr* addr = atoaddr( m_Host.c_str() );
|
|
||||||
if( !addr )
|
|
||||||
throw Wobbly( "Invalid network address" );
|
|
||||||
|
|
||||||
sockaddr_in address;
|
|
||||||
memset( (char*)&address, 0, sizeof(address) );
|
|
||||||
address.sin_family = AF_INET;
|
|
||||||
address.sin_port = htons( m_Port );
|
|
||||||
address.sin_addr.s_addr = addr->s_addr;
|
|
||||||
|
|
||||||
m_Sock = socket( AF_INET, SOCK_STREAM, 0 );
|
|
||||||
if( m_Sock < 0 )
|
|
||||||
BailOnSocketError( "socket()" );
|
|
||||||
|
|
||||||
// printf("Connecting to %s on port %d.\n",inet_ntoa(*addr), port);
|
|
||||||
|
|
||||||
if( ::connect( m_Sock, (sockaddr const*)&address, sizeof(address) ) < 0 )
|
|
||||||
BailOnSocketError( "connect()" );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Connection::close()
|
|
||||||
{
|
|
||||||
#ifdef WIN32
|
|
||||||
if( m_Sock >= 0 )
|
|
||||||
::closesocket( m_Sock );
|
|
||||||
#else
|
|
||||||
if( m_Sock >= 0 )
|
|
||||||
::close( m_Sock );
|
|
||||||
#endif
|
|
||||||
m_Sock = -1;
|
|
||||||
|
|
||||||
// discard any incomplete responses
|
|
||||||
while( !m_Outstanding.empty() )
|
|
||||||
{
|
|
||||||
delete m_Outstanding.front();
|
|
||||||
m_Outstanding.pop_front();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Connection::~Connection()
|
|
||||||
{
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Connection::request( const char* method,
|
|
||||||
const char* url,
|
|
||||||
const char* headers[],
|
|
||||||
const unsigned char* body,
|
|
||||||
int bodysize )
|
|
||||||
{
|
|
||||||
|
|
||||||
bool gotcontentlength = false; // already in headers?
|
|
||||||
|
|
||||||
// check headers for content-length
|
|
||||||
// TODO: check for "Host" and "Accept-Encoding" too
|
|
||||||
// and avoid adding them ourselves in putrequest()
|
|
||||||
if( headers )
|
|
||||||
{
|
|
||||||
const char** h = headers;
|
|
||||||
while( *h )
|
|
||||||
{
|
|
||||||
const char* name = *h++;
|
|
||||||
const char* value = *h++;
|
|
||||||
assert( value != 0 ); // name with no value!
|
|
||||||
|
|
||||||
if( 0==_stricmp( name, "content-length" ) )
|
|
||||||
gotcontentlength = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
putrequest( method, url );
|
|
||||||
|
|
||||||
if( body && !gotcontentlength )
|
|
||||||
putheader( "Content-Length", bodysize );
|
|
||||||
|
|
||||||
if( headers )
|
|
||||||
{
|
|
||||||
const char** h = headers;
|
|
||||||
while( *h )
|
|
||||||
{
|
|
||||||
const char* name = *h++;
|
|
||||||
const char* value = *h++;
|
|
||||||
putheader( name, value );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
endheaders();
|
|
||||||
|
|
||||||
if( body )
|
|
||||||
send( body, bodysize );
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void Connection::putrequest( const char* method, const char* url )
|
|
||||||
{
|
|
||||||
if( m_State != IDLE )
|
|
||||||
throw Wobbly( "Request already issued" );
|
|
||||||
|
|
||||||
m_State = REQ_STARTED;
|
|
||||||
|
|
||||||
char req[ 512 ];
|
|
||||||
sprintf( req, "%s %s HTTP/1.1", method, url );
|
|
||||||
m_Buffer.push_back( req );
|
|
||||||
|
|
||||||
putheader( "Host", m_Host.c_str() ); // required for HTTP1.1
|
|
||||||
|
|
||||||
// don't want any fancy encodings please
|
|
||||||
putheader("Accept-Encoding", "identity");
|
|
||||||
|
|
||||||
// Push a new response onto the queue
|
|
||||||
Response *r = new Response( method, *this );
|
|
||||||
m_Outstanding.push_back( r );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Connection::putheader( const char* header, const char* value )
|
|
||||||
{
|
|
||||||
if( m_State != REQ_STARTED )
|
|
||||||
throw Wobbly( "putheader() failed" );
|
|
||||||
m_Buffer.push_back( string(header) + ": " + string( value ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
void Connection::putheader( const char* header, int numericvalue )
|
|
||||||
{
|
|
||||||
char buf[32];
|
|
||||||
sprintf( buf, "%d", numericvalue );
|
|
||||||
putheader( header, buf );
|
|
||||||
}
|
|
||||||
|
|
||||||
void Connection::endheaders()
|
|
||||||
{
|
|
||||||
if( m_State != REQ_STARTED )
|
|
||||||
throw Wobbly( "Cannot send header" );
|
|
||||||
m_State = IDLE;
|
|
||||||
|
|
||||||
m_Buffer.push_back( "" );
|
|
||||||
|
|
||||||
string msg;
|
|
||||||
vector< string>::const_iterator it;
|
|
||||||
for( it = m_Buffer.begin(); it != m_Buffer.end(); ++it )
|
|
||||||
msg += (*it) + "\r\n";
|
|
||||||
|
|
||||||
m_Buffer.clear();
|
|
||||||
|
|
||||||
// printf( "%s", msg.c_str() );
|
|
||||||
send( (const unsigned char*)msg.c_str(), msg.size() );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void Connection::send( const unsigned char* buf, int numbytes )
|
|
||||||
{
|
|
||||||
// fwrite( buf, 1,numbytes, stdout );
|
|
||||||
|
|
||||||
if( m_Sock < 0 )
|
|
||||||
connect();
|
|
||||||
|
|
||||||
while( numbytes > 0 )
|
|
||||||
{
|
|
||||||
#ifdef WIN32
|
|
||||||
int n = ::send( m_Sock, (const char*)buf, numbytes, 0 );
|
|
||||||
#else
|
|
||||||
int n = ::send( m_Sock, buf, numbytes, 0 );
|
|
||||||
#endif
|
|
||||||
if( n<0 )
|
|
||||||
BailOnSocketError( "send()" );
|
|
||||||
numbytes -= n;
|
|
||||||
buf += n;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Connection::pump(int milisec)
|
|
||||||
{
|
|
||||||
if( m_Outstanding.empty() )
|
|
||||||
return; // no requests outstanding
|
|
||||||
|
|
||||||
assert( m_Sock >0 ); // outstanding requests but no connection!
|
|
||||||
|
|
||||||
if( !datawaiting( m_Sock ) )
|
|
||||||
return; // recv will block
|
|
||||||
|
|
||||||
unsigned char buf[ 2048 ];
|
|
||||||
int a = recv( m_Sock, (char*)buf, sizeof(buf), 0 );
|
|
||||||
if( a<0 )
|
|
||||||
BailOnSocketError( "recv()" );
|
|
||||||
|
|
||||||
if( a== 0 )
|
|
||||||
{
|
|
||||||
// connection has closed
|
|
||||||
|
|
||||||
Response* r = m_Outstanding.front();
|
|
||||||
r->notifyconnectionclosed();
|
|
||||||
assert( r->completed() );
|
|
||||||
delete r;
|
|
||||||
m_Outstanding.pop_front();
|
|
||||||
|
|
||||||
// any outstanding requests will be discarded
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int used = 0;
|
|
||||||
while( used < a && !m_Outstanding.empty() )
|
|
||||||
{
|
|
||||||
|
|
||||||
Response* r = m_Outstanding.front();
|
|
||||||
int u = r->pump( &buf[used], a-used );
|
|
||||||
|
|
||||||
// delete response once completed
|
|
||||||
if( r->completed() )
|
|
||||||
{
|
|
||||||
delete r;
|
|
||||||
m_Outstanding.pop_front();
|
|
||||||
}
|
|
||||||
used += u;
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: will lose bytes if response queue goes empty
|
|
||||||
// (but server shouldn't be sending anything if we don't have
|
|
||||||
// anything outstanding anyway)
|
|
||||||
assert( used == a ); // all bytes should be used up by here.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//---------------------------------------------------------------------
|
|
||||||
//
|
|
||||||
// Response
|
|
||||||
//
|
|
||||||
//---------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
Response::Response( const char* method, Connection& conn ) :
|
|
||||||
m_Connection( conn ),
|
|
||||||
m_State( STATUSLINE ),
|
|
||||||
m_Method( method ),
|
|
||||||
m_Version( 0 ),
|
|
||||||
m_Status(0),
|
|
||||||
m_BytesRead(0),
|
|
||||||
m_Chunked(false),
|
|
||||||
m_ChunkLeft(0),
|
|
||||||
m_Length(-1),
|
|
||||||
m_WillClose(false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const char* Response::getheader( const char* name ) const
|
|
||||||
{
|
|
||||||
std::string lname( name );
|
|
||||||
#ifdef _MSC_VER
|
|
||||||
std::transform( lname.begin(), lname.end(), lname.begin(), tolower );
|
|
||||||
#else
|
|
||||||
std::transform( lname.begin(), lname.end(), lname.begin(), ::tolower );
|
|
||||||
#endif
|
|
||||||
|
|
||||||
std::map< std::string, std::string >::const_iterator it = m_Headers.find( lname );
|
|
||||||
if( it == m_Headers.end() )
|
|
||||||
return 0;
|
|
||||||
else
|
|
||||||
return it->second.c_str();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int Response::getstatus() const
|
|
||||||
{
|
|
||||||
// only valid once we've got the statusline
|
|
||||||
assert( m_State != STATUSLINE );
|
|
||||||
return m_Status;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const char* Response::getreason() const
|
|
||||||
{
|
|
||||||
// only valid once we've got the statusline
|
|
||||||
assert( m_State != STATUSLINE );
|
|
||||||
return m_Reason.c_str();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Connection has closed
|
|
||||||
void Response::notifyconnectionclosed()
|
|
||||||
{
|
|
||||||
if( m_State == COMPLETE )
|
|
||||||
return;
|
|
||||||
|
|
||||||
// eof can be valid...
|
|
||||||
if( m_State == BODY &&
|
|
||||||
!m_Chunked &&
|
|
||||||
m_Length == -1 )
|
|
||||||
{
|
|
||||||
Finish(); // we're all done!
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw Wobbly( "Connection closed unexpectedly" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int Response::pump( const unsigned char* data, int datasize )
|
|
||||||
{
|
|
||||||
assert( datasize != 0 );
|
|
||||||
int count = datasize;
|
|
||||||
|
|
||||||
while( count > 0 && m_State != COMPLETE )
|
|
||||||
{
|
|
||||||
if( m_State == STATUSLINE ||
|
|
||||||
m_State == HEADERS ||
|
|
||||||
m_State == TRAILERS ||
|
|
||||||
m_State == CHUNKLEN ||
|
|
||||||
m_State == CHUNKEND )
|
|
||||||
{
|
|
||||||
// we want to accumulate a line
|
|
||||||
while( count > 0 )
|
|
||||||
{
|
|
||||||
char c = (char)*data++;
|
|
||||||
--count;
|
|
||||||
if( c == '\n' )
|
|
||||||
{
|
|
||||||
// now got a whole line!
|
|
||||||
switch( m_State )
|
|
||||||
{
|
|
||||||
case STATUSLINE:
|
|
||||||
ProcessStatusLine( m_LineBuf );
|
|
||||||
break;
|
|
||||||
case HEADERS:
|
|
||||||
ProcessHeaderLine( m_LineBuf );
|
|
||||||
break;
|
|
||||||
case TRAILERS:
|
|
||||||
ProcessTrailerLine( m_LineBuf );
|
|
||||||
break;
|
|
||||||
case CHUNKLEN:
|
|
||||||
ProcessChunkLenLine( m_LineBuf );
|
|
||||||
break;
|
|
||||||
case CHUNKEND:
|
|
||||||
// just soak up the crlf after body and go to next state
|
|
||||||
assert( m_Chunked == true );
|
|
||||||
m_State = CHUNKLEN;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
m_LineBuf.clear();
|
|
||||||
break; // break out of line accumulation!
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if( c != '\r' ) // just ignore CR
|
|
||||||
m_LineBuf += c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if( m_State == BODY )
|
|
||||||
{
|
|
||||||
int bytesused = 0;
|
|
||||||
if( m_Chunked )
|
|
||||||
bytesused = ProcessDataChunked( data, count );
|
|
||||||
else
|
|
||||||
bytesused = ProcessDataNonChunked( data, count );
|
|
||||||
data += bytesused;
|
|
||||||
count -= bytesused;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// return number of bytes used
|
|
||||||
return datasize - count;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void Response::ProcessChunkLenLine( std::string const& line )
|
|
||||||
{
|
|
||||||
// chunklen in hex at beginning of line
|
|
||||||
m_ChunkLeft = strtol( line.c_str(), NULL, 16 );
|
|
||||||
|
|
||||||
if( m_ChunkLeft == 0 )
|
|
||||||
{
|
|
||||||
// got the whole body, now check for trailing headers
|
|
||||||
m_State = TRAILERS;
|
|
||||||
m_HeaderAccum.clear();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_State = BODY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// handle some body data in chunked mode
|
|
||||||
// returns number of bytes used.
|
|
||||||
int Response::ProcessDataChunked( const unsigned char* data, int count )
|
|
||||||
{
|
|
||||||
assert( m_Chunked );
|
|
||||||
|
|
||||||
int n = count;
|
|
||||||
if( n>m_ChunkLeft )
|
|
||||||
n = m_ChunkLeft;
|
|
||||||
|
|
||||||
// invoke callback to pass out the data
|
|
||||||
if( m_Connection.m_ResponseDataCB )
|
|
||||||
(m_Connection.m_ResponseDataCB)( this, m_Connection.m_UserData, data, n );
|
|
||||||
|
|
||||||
m_BytesRead += n;
|
|
||||||
|
|
||||||
m_ChunkLeft -= n;
|
|
||||||
assert( m_ChunkLeft >= 0);
|
|
||||||
if( m_ChunkLeft == 0 )
|
|
||||||
{
|
|
||||||
// chunk completed! now soak up the trailing CRLF before next chunk
|
|
||||||
m_State = CHUNKEND;
|
|
||||||
}
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle some body data in non-chunked mode.
|
|
||||||
// returns number of bytes used.
|
|
||||||
int Response::ProcessDataNonChunked( const unsigned char* data, int count )
|
|
||||||
{
|
|
||||||
int n = count;
|
|
||||||
if( m_Length != -1 )
|
|
||||||
{
|
|
||||||
// we know how many bytes to expect
|
|
||||||
int remaining = m_Length - m_BytesRead;
|
|
||||||
if( n > remaining )
|
|
||||||
n = remaining;
|
|
||||||
}
|
|
||||||
|
|
||||||
// invoke callback to pass out the data
|
|
||||||
if( m_Connection.m_ResponseDataCB )
|
|
||||||
(m_Connection.m_ResponseDataCB)( this, m_Connection.m_UserData, data, n );
|
|
||||||
|
|
||||||
m_BytesRead += n;
|
|
||||||
|
|
||||||
// Finish if we know we're done. Else we're waiting for connection close.
|
|
||||||
if( m_Length != -1 && m_BytesRead == m_Length )
|
|
||||||
Finish();
|
|
||||||
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Response::Finish()
|
|
||||||
{
|
|
||||||
m_State = COMPLETE;
|
|
||||||
|
|
||||||
// invoke the callback
|
|
||||||
if( m_Connection.m_ResponseCompleteCB )
|
|
||||||
(m_Connection.m_ResponseCompleteCB)( this, m_Connection.m_UserData );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Response::ProcessStatusLine( std::string const& line )
|
|
||||||
{
|
|
||||||
const char* p = line.c_str();
|
|
||||||
|
|
||||||
// skip any leading space
|
|
||||||
while( *p && *p == ' ' )
|
|
||||||
++p;
|
|
||||||
|
|
||||||
// get version
|
|
||||||
while( *p && *p != ' ' )
|
|
||||||
m_VersionString += *p++;
|
|
||||||
while( *p && *p == ' ' )
|
|
||||||
++p;
|
|
||||||
|
|
||||||
// get status code
|
|
||||||
std::string status;
|
|
||||||
while( *p && *p != ' ' )
|
|
||||||
status += *p++;
|
|
||||||
while( *p && *p == ' ' )
|
|
||||||
++p;
|
|
||||||
|
|
||||||
// rest of line is reason
|
|
||||||
while( *p )
|
|
||||||
m_Reason += *p++;
|
|
||||||
|
|
||||||
m_Status = atoi( status.c_str() );
|
|
||||||
if( m_Status < 100 || m_Status > 999 )
|
|
||||||
throw Wobbly( "BadStatusLine (%s)", line.c_str() );
|
|
||||||
|
|
||||||
/*
|
|
||||||
printf( "version: '%s'\n", m_VersionString.c_str() );
|
|
||||||
printf( "status: '%d'\n", m_Status );
|
|
||||||
printf( "reason: '%s'\n", m_Reason.c_str() );
|
|
||||||
*/
|
|
||||||
|
|
||||||
if( m_VersionString == "HTTP:/1.0" )
|
|
||||||
m_Version = 10;
|
|
||||||
else if( 0==m_VersionString.compare( 0,7,"HTTP/1." ) )
|
|
||||||
m_Version = 11;
|
|
||||||
else
|
|
||||||
throw Wobbly( "UnknownProtocol (%s)", m_VersionString.c_str() );
|
|
||||||
// TODO: support for HTTP/0.9
|
|
||||||
|
|
||||||
|
|
||||||
// OK, now we expect headers!
|
|
||||||
m_State = HEADERS;
|
|
||||||
m_HeaderAccum.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// process accumulated header data
|
|
||||||
void Response::FlushHeader()
|
|
||||||
{
|
|
||||||
if( m_HeaderAccum.empty() )
|
|
||||||
return; // no flushing required
|
|
||||||
|
|
||||||
const char* p = m_HeaderAccum.c_str();
|
|
||||||
|
|
||||||
std::string header;
|
|
||||||
std::string value;
|
|
||||||
while( *p && *p != ':' )
|
|
||||||
header += tolower( *p++ );
|
|
||||||
|
|
||||||
// skip ':'
|
|
||||||
if( *p )
|
|
||||||
++p;
|
|
||||||
|
|
||||||
// skip space
|
|
||||||
while( *p && (*p ==' ' || *p=='\t') )
|
|
||||||
++p;
|
|
||||||
|
|
||||||
value = p; // rest of line is value
|
|
||||||
|
|
||||||
m_Headers[ header ] = value;
|
|
||||||
// printf("header: ['%s': '%s']\n", header.c_str(), value.c_str() );
|
|
||||||
|
|
||||||
m_HeaderAccum.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Response::ProcessHeaderLine( std::string const& line )
|
|
||||||
{
|
|
||||||
const char* p = line.c_str();
|
|
||||||
if( line.empty() )
|
|
||||||
{
|
|
||||||
FlushHeader();
|
|
||||||
// end of headers
|
|
||||||
|
|
||||||
// HTTP code 100 handling (we ignore 'em)
|
|
||||||
if( m_Status == CONTINUE )
|
|
||||||
m_State = STATUSLINE; // reset parsing, expect new status line
|
|
||||||
else
|
|
||||||
BeginBody(); // start on body now!
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( isspace(*p) )
|
|
||||||
{
|
|
||||||
// it's a continuation line - just add it to previous data
|
|
||||||
++p;
|
|
||||||
while( *p && isspace( *p ) )
|
|
||||||
++p;
|
|
||||||
|
|
||||||
m_HeaderAccum += ' ';
|
|
||||||
m_HeaderAccum += p;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// begin a new header
|
|
||||||
FlushHeader();
|
|
||||||
m_HeaderAccum = p;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Response::ProcessTrailerLine( std::string const& line )
|
|
||||||
{
|
|
||||||
// TODO: handle trailers?
|
|
||||||
// (python httplib doesn't seem to!)
|
|
||||||
if( line.empty() )
|
|
||||||
Finish();
|
|
||||||
|
|
||||||
// just ignore all the trailers...
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// OK, we've now got all the headers read in, so we're ready to start
|
|
||||||
// on the body. But we need to see what info we can glean from the headers
|
|
||||||
// first...
|
|
||||||
void Response::BeginBody()
|
|
||||||
{
|
|
||||||
|
|
||||||
m_Chunked = false;
|
|
||||||
m_Length = -1; // unknown
|
|
||||||
m_WillClose = false;
|
|
||||||
|
|
||||||
// using chunked encoding?
|
|
||||||
const char* trenc = getheader( "transfer-encoding" );
|
|
||||||
if( trenc && 0==_stricmp( trenc, "chunked") )
|
|
||||||
{
|
|
||||||
m_Chunked = true;
|
|
||||||
m_ChunkLeft = -1; // unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
m_WillClose = CheckClose();
|
|
||||||
|
|
||||||
// length supplied?
|
|
||||||
const char* contentlen = getheader( "content-length" );
|
|
||||||
if( contentlen && !m_Chunked )
|
|
||||||
{
|
|
||||||
m_Length = atoi( contentlen );
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for various cases where we expect zero-length body
|
|
||||||
if( m_Status == NO_CONTENT ||
|
|
||||||
m_Status == NOT_MODIFIED ||
|
|
||||||
( m_Status >= 100 && m_Status < 200 ) || // 1xx codes have no body
|
|
||||||
m_Method == "HEAD" )
|
|
||||||
{
|
|
||||||
m_Length = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// if we're not using chunked mode, and no length has been specified,
|
|
||||||
// assume connection will close at end.
|
|
||||||
if( !m_WillClose && !m_Chunked && m_Length == -1 )
|
|
||||||
m_WillClose = true;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Invoke the user callback, if any
|
|
||||||
if( m_Connection.m_ResponseBeginCB )
|
|
||||||
(m_Connection.m_ResponseBeginCB)( this, m_Connection.m_UserData );
|
|
||||||
|
|
||||||
/*
|
|
||||||
printf("---------BeginBody()--------\n");
|
|
||||||
printf("Length: %d\n", m_Length );
|
|
||||||
printf("WillClose: %d\n", (int)m_WillClose );
|
|
||||||
printf("Chunked: %d\n", (int)m_Chunked );
|
|
||||||
printf("ChunkLeft: %d\n", (int)m_ChunkLeft );
|
|
||||||
printf("----------------------------\n");
|
|
||||||
*/
|
|
||||||
// now start reading body data!
|
|
||||||
if( m_Chunked )
|
|
||||||
m_State = CHUNKLEN;
|
|
||||||
else
|
|
||||||
m_State = BODY;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// return true if we think server will automatically close connection
|
|
||||||
bool Response::CheckClose()
|
|
||||||
{
|
|
||||||
if( m_Version == 11 )
|
|
||||||
{
|
|
||||||
// HTTP1.1
|
|
||||||
// the connection stays open unless "connection: close" is specified.
|
|
||||||
const char* conn = getheader( "connection" );
|
|
||||||
if( conn && 0==_stricmp( conn, "close" ) )
|
|
||||||
return true;
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Older HTTP
|
|
||||||
// keep-alive header indicates persistant connection
|
|
||||||
if( getheader( "keep-alive" ) )
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// TODO: some special case handling for Akamai and netscape maybe?
|
|
||||||
// (see _check_close() in python httplib.py for details)
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} // end namespace happyhttp
|
|
||||||
|
|
||||||
|
|
333
external/happyhttp/happyhttp/happyhttp.h
vendored
333
external/happyhttp/happyhttp/happyhttp.h
vendored
@ -1,333 +0,0 @@
|
|||||||
/*
|
|
||||||
* HappyHTTP - a simple HTTP library
|
|
||||||
* Version 0.1
|
|
||||||
*
|
|
||||||
* Copyright (c) 2006 Ben Campbell
|
|
||||||
*
|
|
||||||
* This software is provided 'as-is', without any express or implied
|
|
||||||
* warranty. In no event will the authors be held liable for any damages
|
|
||||||
* arising from the use of this software.
|
|
||||||
*
|
|
||||||
* Permission is granted to anyone to use this software for any purpose,
|
|
||||||
* including commercial applications, and to alter it and redistribute it
|
|
||||||
* freely, subject to the following restrictions:
|
|
||||||
*
|
|
||||||
* 1. The origin of this software must not be misrepresented; you must not
|
|
||||||
* claim that you wrote the original software. If you use this software in a
|
|
||||||
* product, an acknowledgment in the product documentation would be
|
|
||||||
* appreciated but is not required.
|
|
||||||
*
|
|
||||||
* 2. Altered source versions must be plainly marked as such, and must not
|
|
||||||
* be misrepresented as being the original software.
|
|
||||||
*
|
|
||||||
* 3. This notice may not be removed or altered from any source distribution.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef HAPPYHTTP_H
|
|
||||||
#define HAPPYHTTP_H
|
|
||||||
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <map>
|
|
||||||
#include <vector>
|
|
||||||
#include <deque>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// forward decl
|
|
||||||
struct in_addr;
|
|
||||||
|
|
||||||
namespace happyhttp
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
class Response;
|
|
||||||
|
|
||||||
// Helper Functions
|
|
||||||
void BailOnSocketError( const char* context );
|
|
||||||
struct in_addr *atoaddr( const char* address);
|
|
||||||
|
|
||||||
|
|
||||||
typedef void (*ResponseBegin_CB)( const Response* r, void* userdata );
|
|
||||||
typedef void (*ResponseData_CB)( const Response* r, void* userdata, const unsigned char* data, int numbytes );
|
|
||||||
typedef void (*ResponseComplete_CB)( const Response* r, void* userdata );
|
|
||||||
|
|
||||||
|
|
||||||
// HTTP status codes
|
|
||||||
enum {
|
|
||||||
// 1xx informational
|
|
||||||
CONTINUE = 100,
|
|
||||||
SWITCHING_PROTOCOLS = 101,
|
|
||||||
PROCESSING = 102,
|
|
||||||
|
|
||||||
// 2xx successful
|
|
||||||
OK = 200,
|
|
||||||
CREATED = 201,
|
|
||||||
ACCEPTED = 202,
|
|
||||||
NON_AUTHORITATIVE_INFORMATION = 203,
|
|
||||||
NO_CONTENT = 204,
|
|
||||||
RESET_CONTENT = 205,
|
|
||||||
PARTIAL_CONTENT = 206,
|
|
||||||
MULTI_STATUS = 207,
|
|
||||||
IM_USED = 226,
|
|
||||||
|
|
||||||
// 3xx redirection
|
|
||||||
MULTIPLE_CHOICES = 300,
|
|
||||||
MOVED_PERMANENTLY = 301,
|
|
||||||
FOUND = 302,
|
|
||||||
SEE_OTHER = 303,
|
|
||||||
NOT_MODIFIED = 304,
|
|
||||||
USE_PROXY = 305,
|
|
||||||
TEMPORARY_REDIRECT = 307,
|
|
||||||
|
|
||||||
// 4xx client error
|
|
||||||
BAD_REQUEST = 400,
|
|
||||||
UNAUTHORIZED = 401,
|
|
||||||
PAYMENT_REQUIRED = 402,
|
|
||||||
FORBIDDEN = 403,
|
|
||||||
NOT_FOUND = 404,
|
|
||||||
METHOD_NOT_ALLOWED = 405,
|
|
||||||
NOT_ACCEPTABLE = 406,
|
|
||||||
PROXY_AUTHENTICATION_REQUIRED = 407,
|
|
||||||
REQUEST_TIMEOUT = 408,
|
|
||||||
CONFLICT = 409,
|
|
||||||
GONE = 410,
|
|
||||||
LENGTH_REQUIRED = 411,
|
|
||||||
PRECONDITION_FAILED = 412,
|
|
||||||
REQUEST_ENTITY_TOO_LARGE = 413,
|
|
||||||
REQUEST_URI_TOO_LONG = 414,
|
|
||||||
UNSUPPORTED_MEDIA_TYPE = 415,
|
|
||||||
REQUESTED_RANGE_NOT_SATISFIABLE = 416,
|
|
||||||
EXPECTATION_FAILED = 417,
|
|
||||||
UNPROCESSABLE_ENTITY = 422,
|
|
||||||
LOCKED = 423,
|
|
||||||
FAILED_DEPENDENCY = 424,
|
|
||||||
UPGRADE_REQUIRED = 426,
|
|
||||||
|
|
||||||
// 5xx server error
|
|
||||||
INTERNAL_SERVER_ERROR = 500,
|
|
||||||
NOT_IMPLEMENTED = 501,
|
|
||||||
BAD_GATEWAY = 502,
|
|
||||||
SERVICE_UNAVAILABLE = 503,
|
|
||||||
GATEWAY_TIMEOUT = 504,
|
|
||||||
HTTP_VERSION_NOT_SUPPORTED = 505,
|
|
||||||
INSUFFICIENT_STORAGE = 507,
|
|
||||||
NOT_EXTENDED = 510,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Exception class
|
|
||||||
|
|
||||||
class Wobbly
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Wobbly( const char* fmt, ... );
|
|
||||||
const char* what() const
|
|
||||||
{ return m_Message; }
|
|
||||||
protected:
|
|
||||||
enum { MAXLEN=256 };
|
|
||||||
char m_Message[ MAXLEN ];
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//-------------------------------------------------
|
|
||||||
// Connection
|
|
||||||
//
|
|
||||||
// Handles the socket connection, issuing of requests and managing
|
|
||||||
// responses.
|
|
||||||
// ------------------------------------------------
|
|
||||||
|
|
||||||
class Connection
|
|
||||||
{
|
|
||||||
friend class Response;
|
|
||||||
public:
|
|
||||||
// doesn't connect immediately
|
|
||||||
Connection( const char* host, int port );
|
|
||||||
~Connection();
|
|
||||||
|
|
||||||
// Set up the response handling callbacks. These will be invoked during
|
|
||||||
// calls to pump().
|
|
||||||
// begincb - called when the responses headers have been received
|
|
||||||
// datacb - called repeatedly to handle body data
|
|
||||||
// completecb - response is completed
|
|
||||||
// userdata is passed as a param to all callbacks.
|
|
||||||
void setcallbacks(
|
|
||||||
ResponseBegin_CB begincb,
|
|
||||||
ResponseData_CB datacb,
|
|
||||||
ResponseComplete_CB completecb,
|
|
||||||
void* userdata );
|
|
||||||
|
|
||||||
// Don't need to call connect() explicitly as issuing a request will
|
|
||||||
// call it automatically if needed.
|
|
||||||
// But it could block (for name lookup etc), so you might prefer to
|
|
||||||
// call it in advance.
|
|
||||||
void connect();
|
|
||||||
|
|
||||||
// close connection, discarding any pending requests.
|
|
||||||
void close();
|
|
||||||
|
|
||||||
// Update the connection (non-blocking)
|
|
||||||
// Just keep calling this regularly to service outstanding requests.
|
|
||||||
void pump(int milisec=10); //10 miliseconds to prevent high cpu load
|
|
||||||
|
|
||||||
// any requests still outstanding?
|
|
||||||
bool outstanding() const
|
|
||||||
{ return !m_Outstanding.empty(); }
|
|
||||||
|
|
||||||
// ---------------------------
|
|
||||||
// high-level request interface
|
|
||||||
// ---------------------------
|
|
||||||
|
|
||||||
// method is "GET", "POST" etc...
|
|
||||||
// url is only path part: eg "/index.html"
|
|
||||||
// headers is array of name/value pairs, terminated by a null-ptr
|
|
||||||
// body & bodysize specify body data of request (eg values for a form)
|
|
||||||
void request( const char* method, const char* url, const char* headers[]=0,
|
|
||||||
const unsigned char* body=0, int bodysize=0 );
|
|
||||||
|
|
||||||
// ---------------------------
|
|
||||||
// low-level request interface
|
|
||||||
// ---------------------------
|
|
||||||
|
|
||||||
// begin request
|
|
||||||
// method is "GET", "POST" etc...
|
|
||||||
// url is only path part: eg "/index.html"
|
|
||||||
void putrequest( const char* method, const char* url );
|
|
||||||
|
|
||||||
// Add a header to the request (call after putrequest() )
|
|
||||||
void putheader( const char* header, const char* value );
|
|
||||||
void putheader( const char* header, int numericvalue ); // alternate version
|
|
||||||
|
|
||||||
// Finished adding headers, issue the request.
|
|
||||||
void endheaders();
|
|
||||||
|
|
||||||
// send body data if any.
|
|
||||||
// To be called after endheaders()
|
|
||||||
void send( const unsigned char* buf, int numbytes );
|
|
||||||
|
|
||||||
protected:
|
|
||||||
// some bits of implementation exposed to Response class
|
|
||||||
|
|
||||||
// callbacks
|
|
||||||
ResponseBegin_CB m_ResponseBeginCB;
|
|
||||||
ResponseData_CB m_ResponseDataCB;
|
|
||||||
ResponseComplete_CB m_ResponseCompleteCB;
|
|
||||||
void* m_UserData;
|
|
||||||
|
|
||||||
private:
|
|
||||||
enum { IDLE, REQ_STARTED, REQ_SENT } m_State;
|
|
||||||
std::string m_Host;
|
|
||||||
int m_Port;
|
|
||||||
int m_Sock;
|
|
||||||
std::vector< std::string > m_Buffer; // lines of request
|
|
||||||
|
|
||||||
std::deque< Response* > m_Outstanding; // responses for outstanding requests
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//-------------------------------------------------
|
|
||||||
// Response
|
|
||||||
//
|
|
||||||
// Handles parsing of response data.
|
|
||||||
// ------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
class Response
|
|
||||||
{
|
|
||||||
friend class Connection;
|
|
||||||
public:
|
|
||||||
|
|
||||||
// retrieve a header (returns 0 if not present)
|
|
||||||
const char* getheader( const char* name ) const;
|
|
||||||
|
|
||||||
bool completed() const
|
|
||||||
{ return m_State == COMPLETE; }
|
|
||||||
|
|
||||||
|
|
||||||
// get the HTTP status code
|
|
||||||
int getstatus() const;
|
|
||||||
|
|
||||||
// get the HTTP response reason string
|
|
||||||
const char* getreason() const;
|
|
||||||
|
|
||||||
// true if connection is expected to close after this response.
|
|
||||||
bool willclose() const
|
|
||||||
{ return m_WillClose; }
|
|
||||||
protected:
|
|
||||||
// interface used by Connection
|
|
||||||
|
|
||||||
// only Connection creates Responses.
|
|
||||||
Response( const char* method, Connection& conn );
|
|
||||||
|
|
||||||
// pump some data in for processing.
|
|
||||||
// Returns the number of bytes used.
|
|
||||||
// Will always return 0 when response is complete.
|
|
||||||
int pump( const unsigned char* data, int datasize );
|
|
||||||
|
|
||||||
// tell response that connection has closed
|
|
||||||
void notifyconnectionclosed();
|
|
||||||
|
|
||||||
private:
|
|
||||||
enum {
|
|
||||||
STATUSLINE, // start here. status line is first line of response.
|
|
||||||
HEADERS, // reading in header lines
|
|
||||||
BODY, // waiting for some body data (all or a chunk)
|
|
||||||
CHUNKLEN, // expecting a chunk length indicator (in hex)
|
|
||||||
CHUNKEND, // got the chunk, now expecting a trailing blank line
|
|
||||||
TRAILERS, // reading trailers after body.
|
|
||||||
COMPLETE, // response is complete!
|
|
||||||
} m_State;
|
|
||||||
|
|
||||||
Connection& m_Connection; // to access callback ptrs
|
|
||||||
std::string m_Method; // req method: "GET", "POST" etc...
|
|
||||||
|
|
||||||
// status line
|
|
||||||
std::string m_VersionString; // HTTP-Version
|
|
||||||
int m_Version; // 10: HTTP/1.0 11: HTTP/1.x (where x>=1)
|
|
||||||
int m_Status; // Status-Code
|
|
||||||
std::string m_Reason; // Reason-Phrase
|
|
||||||
|
|
||||||
// header/value pairs
|
|
||||||
std::map<std::string,std::string> m_Headers;
|
|
||||||
|
|
||||||
int m_BytesRead; // body bytes read so far
|
|
||||||
bool m_Chunked; // response is chunked?
|
|
||||||
int m_ChunkLeft; // bytes left in current chunk
|
|
||||||
int m_Length; // -1 if unknown
|
|
||||||
bool m_WillClose; // connection will close at response end?
|
|
||||||
|
|
||||||
std::string m_LineBuf; // line accumulation for states that want it
|
|
||||||
std::string m_HeaderAccum; // accumulation buffer for headers
|
|
||||||
|
|
||||||
|
|
||||||
void FlushHeader();
|
|
||||||
void ProcessStatusLine( std::string const& line );
|
|
||||||
void ProcessHeaderLine( std::string const& line );
|
|
||||||
void ProcessTrailerLine( std::string const& line );
|
|
||||||
void ProcessChunkLenLine( std::string const& line );
|
|
||||||
|
|
||||||
int ProcessDataChunked( const unsigned char* data, int count );
|
|
||||||
int ProcessDataNonChunked( const unsigned char* data, int count );
|
|
||||||
|
|
||||||
void BeginBody();
|
|
||||||
bool CheckClose();
|
|
||||||
void Finish();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} // end namespace happyhttp
|
|
||||||
|
|
||||||
|
|
||||||
#endif // HAPPYHTTP_H
|
|
||||||
|
|
||||||
|
|
@ -7,7 +7,7 @@ add_library(solanaceae_sdbot-webui STATIC
|
|||||||
|
|
||||||
target_compile_features(solanaceae_sdbot-webui PUBLIC cxx_std_17)
|
target_compile_features(solanaceae_sdbot-webui PUBLIC cxx_std_17)
|
||||||
target_link_libraries(solanaceae_sdbot-webui PUBLIC
|
target_link_libraries(solanaceae_sdbot-webui PUBLIC
|
||||||
happyhttp
|
httplib::httplib
|
||||||
solanaceae_contact
|
solanaceae_contact
|
||||||
solanaceae_message3
|
solanaceae_message3
|
||||||
nlohmann_json::nlohmann_json
|
nlohmann_json::nlohmann_json
|
||||||
|
117
src/sd_bot.cpp
117
src/sd_bot.cpp
@ -8,9 +8,9 @@
|
|||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
#include <sodium.h>
|
#include <sodium.h>
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
@ -186,31 +186,29 @@ SDBot::~SDBot(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
float SDBot::iterate(void) {
|
float SDBot::iterate(void) {
|
||||||
if (static_cast<bool>(_con) && _con->outstanding()) {
|
if (_current_task.has_value() != _curr_future.has_value()) {
|
||||||
_con->pump();
|
std::cerr << "SDB inconsistent state\n";
|
||||||
} else if (!_prompt_queue.empty()) { // dequeue new task
|
|
||||||
|
if (_current_task.has_value()) {
|
||||||
|
_task_map.erase(_current_task.value());
|
||||||
|
_current_task = std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_current_task.has_value()) {
|
||||||
|
_curr_future.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_prompt_queue.empty() && !_current_task.has_value()) { // dequeue new task
|
||||||
const auto& [task_id, prompt] = _prompt_queue.front();
|
const auto& [task_id, prompt] = _prompt_queue.front();
|
||||||
|
|
||||||
_current_task = task_id;
|
_current_task = task_id;
|
||||||
|
|
||||||
// TODO: reuse connection?
|
if (_cli == nullptr) {
|
||||||
const std::string server_host {_conf.get_string("SDBot", "server_host").value()};
|
const std::string server_host {_conf.get_string("SDBot", "server_host").value()};
|
||||||
_con = std::make_unique<happyhttp::Connection>(
|
_cli = std::make_unique<httplib::Client>(server_host, _conf.get_int("SDBot", "server_port").value());
|
||||||
server_host.c_str(),
|
_cli->set_read_timeout(std::chrono::minutes(5));
|
||||||
_conf.get_int("SDBot", "server_port").value()
|
}
|
||||||
);
|
|
||||||
_con->setcallbacks(
|
|
||||||
+[](const happyhttp::Response* r, void* ud) { static_cast<SDBot*>(ud)->onHttpBegin(r); },
|
|
||||||
+[](const happyhttp::Response* r, void* ud, const uint8_t* data, int n) { static_cast<SDBot*>(ud)->onHttpData(r, data, n); },
|
|
||||||
+[](const happyhttp::Response* r, void* ud) { static_cast<SDBot*>(ud)->onHttpComplete(r); },
|
|
||||||
this
|
|
||||||
);
|
|
||||||
|
|
||||||
static const char* headers [] {
|
|
||||||
"accept: application/json",
|
|
||||||
"Content-Type: application/json",
|
|
||||||
nullptr,
|
|
||||||
};
|
|
||||||
|
|
||||||
nlohmann::json j_body;
|
nlohmann::json j_body;
|
||||||
j_body["width"] = _conf.get_int("SDBot", "width").value_or(512);
|
j_body["width"] = _conf.get_int("SDBot", "width").value_or(512);
|
||||||
@ -246,20 +244,55 @@ float SDBot::iterate(void) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const std::string url {_conf.get_string("SDBot", "url_txt2img").value()};
|
const std::string url {_conf.get_string("SDBot", "url_txt2img").value()};
|
||||||
_con->request("POST", url.c_str(), headers, reinterpret_cast<const uint8_t*>(body.data()), body.size());
|
_curr_future = std::async(std::launch::async, [this, url, body]() -> std::vector<uint8_t> {
|
||||||
} catch (const happyhttp::Wobbly& e) {
|
// TODO: move to endpoint
|
||||||
std::cerr << "SDB http request error: " << e.what() << "\n";
|
auto res = _cli->Post(url, body, "application/json");
|
||||||
|
std::cout << "SDB http complete " << res->status << " " << res->reason << "\n";
|
||||||
|
if (
|
||||||
|
res.error() != httplib::Error::Success ||
|
||||||
|
res->status != 200
|
||||||
|
) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::vector<uint8_t>(res->body.cbegin(), res->body.cend());
|
||||||
|
});
|
||||||
|
} catch (...) {
|
||||||
|
std::cerr << "SDB http request error\n";
|
||||||
// cleanup
|
// cleanup
|
||||||
_task_map.erase(_current_task.value());
|
_task_map.erase(_current_task.value());
|
||||||
_current_task = std::nullopt;
|
_current_task = std::nullopt;
|
||||||
_con.reset();
|
_curr_future.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
_prompt_queue.pop();
|
_prompt_queue.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (_curr_future.has_value() && _curr_future.value().wait_for(std::chrono::milliseconds(1)) == std::future_status::ready) {
|
||||||
|
const auto& contact = _task_map.at(_current_task.value());
|
||||||
|
|
||||||
|
const auto data = _curr_future.value().get();
|
||||||
|
|
||||||
|
if (_endpoint->handleResponse(contact, ByteSpan{data})) {
|
||||||
|
// TODO: error handling
|
||||||
|
}
|
||||||
|
|
||||||
|
_task_map.erase(_current_task.value());
|
||||||
|
_current_task = std::nullopt;
|
||||||
|
_curr_future.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// if active web connection, 5ms
|
// if active web connection, 5ms
|
||||||
return static_cast<bool>(_con) ? 0.005f : 1.f;
|
//return static_cast<bool>(_con) ? 0.005f : 1.f;
|
||||||
|
|
||||||
|
// if active web connection, 50ms
|
||||||
|
if (_curr_future.has_value() && _curr_future.value().valid()) {
|
||||||
|
return 0.05f;
|
||||||
|
} else {
|
||||||
|
return 1.f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SDBot::onEvent(const Message::Events::MessageConstruct& e) {
|
bool SDBot::onEvent(const Message::Events::MessageConstruct& e) {
|
||||||
@ -336,33 +369,3 @@ bool SDBot::onEvent(const Message::Events::MessageConstruct& e) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SDBot::onHttpBegin(const happyhttp::Response* r) {
|
|
||||||
std::cout << "SDB http begin " << r->getstatus() << " " << r->getreason() << "\n";
|
|
||||||
// TODO: handle errors
|
|
||||||
_con_data.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SDBot::onHttpData(const happyhttp::Response* /*r*/, const unsigned char* data, int n) {
|
|
||||||
//std::cout << "SDB http data\n";
|
|
||||||
// TODO: handle errors
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
_con_data.push_back(data[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SDBot::onHttpComplete(const happyhttp::Response* r) {
|
|
||||||
std::cout << "SDB http complete " << r->getstatus() << " " << r->getreason() << "\n";
|
|
||||||
if (r->getstatus() == happyhttp::OK) {
|
|
||||||
std::cout << "SDB data\n";
|
|
||||||
|
|
||||||
const auto& contact = _task_map.at(_current_task.value());
|
|
||||||
if (_endpoint->handleResponse(contact, ByteSpan{_con_data})) {
|
|
||||||
// error?
|
|
||||||
}
|
|
||||||
|
|
||||||
_task_map.erase(_current_task.value());
|
|
||||||
_current_task = std::nullopt;
|
|
||||||
_con.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -4,14 +4,16 @@
|
|||||||
#include <solanaceae/message3/registry_message_model.hpp>
|
#include <solanaceae/message3/registry_message_model.hpp>
|
||||||
#include <solanaceae/contact/contact_model3.hpp>
|
#include <solanaceae/contact/contact_model3.hpp>
|
||||||
|
|
||||||
#include <happyhttp/happyhttp.h>
|
#include <httplib.h>
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
#include <future>
|
||||||
|
|
||||||
// fwd
|
// fwd
|
||||||
struct ConfigModelI;
|
struct ConfigModelI;
|
||||||
@ -29,8 +31,8 @@ class SDBot : public RegistryMessageModelEventI {
|
|||||||
uint64_t _last_task_counter = 0;
|
uint64_t _last_task_counter = 0;
|
||||||
|
|
||||||
std::optional<uint64_t> _current_task;
|
std::optional<uint64_t> _current_task;
|
||||||
std::unique_ptr<happyhttp::Connection> _con;
|
std::unique_ptr<httplib::Client> _cli;
|
||||||
std::vector<uint8_t> _con_data;
|
std::optional<std::future<std::vector<uint8_t>>> _curr_future;
|
||||||
|
|
||||||
std::default_random_engine _rng;
|
std::default_random_engine _rng;
|
||||||
|
|
||||||
@ -66,10 +68,5 @@ class SDBot : public RegistryMessageModelEventI {
|
|||||||
//bool onToxEvent(const Tox_Event_Group_Message* e) override;
|
//bool onToxEvent(const Tox_Event_Group_Message* e) override;
|
||||||
protected: // mm
|
protected: // mm
|
||||||
bool onEvent(const Message::Events::MessageConstruct& e) override;
|
bool onEvent(const Message::Events::MessageConstruct& e) override;
|
||||||
|
|
||||||
public: // http cb
|
|
||||||
void onHttpBegin(const happyhttp::Response* r);
|
|
||||||
void onHttpData(const happyhttp::Response* r, const unsigned char* data, int n);
|
|
||||||
void onHttpComplete(const happyhttp::Response* r);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user