From: Mikko Rasa Date: Sun, 16 Nov 2008 12:42:29 +0000 (+0000) Subject: Initial revision X-Git-Url: http://git.tdb.fi/?p=libs%2Fnet.git;a=commitdiff_plain;h=070d56e7b0036ca2e4234eb06dcae83ebfb3df34 Initial revision --- diff --git a/Build b/Build new file mode 100644 index 0000000..f602417 --- /dev/null +++ b/Build @@ -0,0 +1,15 @@ +package "msphttp" +{ + description "HTTP client and server library"; + version "0.1"; + + require "mspnet"; + require "mspstrings"; + + library "msphttp" + { + source "source"; + install true; + install_headers "msp/http"; + }; +}; diff --git a/source/client.cpp b/source/client.cpp new file mode 100644 index 0000000..fde7c8d --- /dev/null +++ b/source/client.cpp @@ -0,0 +1,153 @@ +/* $Id$ + +This file is part of libmsphttp +Copyright © 2008 Mikkosoft Productions, Mikko Rasa +Distributed under the LGPL +*/ + +#include +#include +#include +#include +#include "client.h" +#include "request.h" +#include "response.h" + +using namespace std; + +#include + +namespace Msp { +namespace Http { + +Client::Client(): + sock(0), + event_disp(0), + request(0), + response(0) +{ } + +Client::~Client() +{ + delete sock; + delete request; + delete response; +} + +void Client::use_event_dispatcher(IO::EventDispatcher *ed) +{ + event_disp=ed; + if(sock) + event_disp->add(*sock); +} + +void Client::start_request(const Request &r) +{ + if(request) + throw InvalidState("Already processing a request"); + + RefPtr addr=Net::resolve(r.get_header("host")+":"+r.get_header("x-port")); + + delete sock; + sock=new Net::StreamSocket(addr->get_family()); + sock->set_block(false); + + sock->signal_data_available.connect(sigc::mem_fun(this, &Client::data_available)); + sock->signal_connect_finished.connect(sigc::mem_fun(this, &Client::connect_finished)); + if(event_disp) + event_disp->add(*sock); + + sock->connect(*addr); + + request=new Request(r); + + delete response; + response=0; + in_buf.clear(); +} + +void Client::get_url(const std::string &url) +{ + start_request(Request::from_url(url)); + wait_response(); +} + +void Client::tick() +{ + if(!request) + return; + + while(IO::PollEvent ev=IO::poll(*sock, sock->get_events(), Time::zero)) + sock->event(ev); + + if(response && response->get_complete()) + { + signal_response_complete.emit(*response); + + delete sock; + sock=0; + delete request; + request=0; + } +} + +void Client::wait_response() +{ + while(!response || !response->get_complete()) + tick(); +} + +void Client::abort() +{ + delete sock; + sock=0; + delete request; + request=0; +} + +void Client::connect_finished(int err) +{ + if(err) + { + signal_socket_error.emit(err); + + sock->close(); + delete request; + request=0; + } + else + sock->write(request->str()); +} + +void Client::data_available() +{ + char rbuf[4096]; + unsigned len=sock->read(rbuf, sizeof(rbuf)); + in_buf.append(rbuf, len); + + if(!response) + { + if(in_buf.find("\r\n\r\n")!=string::npos || in_buf.find("\n\n")!=string::npos) + { + response=new Response(Response::parse(in_buf)); + in_buf=string(); + } + } + else + { + len=response->parse_data(in_buf); + in_buf.erase(0, len); + } + + if(response && response->get_complete()) + { + signal_response_complete.emit(*response); + + sock->close(); + delete request; + request=0; + } +} + +} // namespace Http +} // namespace Msp diff --git a/source/client.h b/source/client.h new file mode 100644 index 0000000..d72cbad --- /dev/null +++ b/source/client.h @@ -0,0 +1,58 @@ +/* $Id$ + +This file is part of libmsphttp +Copyright © 2008 Mikkosoft Productions, Mikko Rasa +Distributed under the LGPL +*/ + +#ifndef MSP_HTTP_CLIENT_H_ +#define MSP_HTTP_CLIENT_H_ + +#include +#include +#include +#include + +namespace Msp { +namespace Http { + +class Request; +class Response; + +class Client +{ +private: + Net::StreamSocket *sock; + IO::EventDispatcher *event_disp; + Request *request; + Response *response; + std::string in_buf; + +public: + sigc::signal signal_response_complete; + sigc::signal signal_socket_error; + +private: + Client(const Client &); + Client &operator=(const Client &); +public: + Client(); + ~Client(); + + void use_event_dispatcher(IO::EventDispatcher *); + void start_request(const Request &); + void get_url(const std::string &); + void tick(); + void wait_response(); + void abort(); + const Request *get_request() const { return request; } + const Response *get_response() const { return response; } +private: + void connect_finished(int); + void data_available(); +}; + +} // namespace Http +} // namespace Msp + +#endif diff --git a/source/message.cpp b/source/message.cpp new file mode 100644 index 0000000..a8851fa --- /dev/null +++ b/source/message.cpp @@ -0,0 +1,113 @@ +/* $Id$ + +This file is part of libmsphttp +Copyright © 2008 Mikkosoft Productions, Mikko Rasa +Distributed under the LGPL +*/ + +#include +#include +#include +#include "message.h" + +using namespace std; + +#include + +namespace Msp { +namespace Http { + +void Message::set_header(const string &hdr, const string &val) +{ + headers[tolower(hdr)]=val; +} + +const string &Message::get_header(const string &hdr) const +{ + HeaderMap::const_iterator i=headers.find(hdr); + if(i==headers.end()) + throw KeyError(format("Header %s is not defined", hdr)); + + return i->second; +} + +void Message::add_data(const string &d) +{ + data+=d; + if(headers.count("content-type")==0) + set_header("content-type", "text/plain"); + set_header("content-length", lexical_cast(data.size())); +} + +unsigned Message::parse_data(const string &d) +{ + if(complete) + return 0; + + HeaderMap::const_iterator i=headers.find("content-length"); + if(i!=headers.end()) + { + unsigned needed=lexical_cast(i->second)-data.size(); + unsigned len=min(needed, d.size()); + + data.append(d, 0, len); + + if(len==needed) + complete=true; + + return len; + } + + i=headers.find("transfer-encoding"); + if(i!=headers.end() && strcasecmp(i->second, "chunked")==0) + { + unsigned pos=0; + while(!complete && posfirst, i->second); + result+="\r\n"; + result+=data; + + return result; +} + +} // namespace Http +} // namespace Msp diff --git a/source/message.h b/source/message.h new file mode 100644 index 0000000..c9dc618 --- /dev/null +++ b/source/message.h @@ -0,0 +1,43 @@ +/* $Id$ + +This file is part of libmsphttp +Copyright © 2008 Mikkosoft Productions, Mikko Rasa +Distributed under the LGPL +*/ + +#ifndef MSP_HTTP_MESSAGE_H_ +#define MSP_HTTP_MESSAGE_H_ + +#include +#include "misc.h" + +namespace Msp { +namespace Http { + +class Message +{ +public: + virtual ~Message() { } + + void set_header(const std::string &, const std::string &); + const std::string &get_header(const std::string &) const; + const std::string &get_data() const { return data; } + bool get_complete() const { return complete; } + void add_data(const std::string &); + unsigned parse_data(const std::string &); + virtual std::string str() const =0; +protected: + Version http_version; + HeaderMap headers; + std::string data; + unsigned chunk_length; + bool complete; + + Message(); + std::string str_common() const; +}; + +} // namespace Http +} // namespace Msp + +#endif diff --git a/source/misc.cpp b/source/misc.cpp new file mode 100644 index 0000000..6ac7c5d --- /dev/null +++ b/source/misc.cpp @@ -0,0 +1,32 @@ +/* $Id$ + +This file is part of libmsphttp +Copyright © 2008 Mikkosoft Productions, Mikko Rasa +Distributed under the LGPL +*/ + +#include +#include +#include +#include "misc.h" + +using namespace std; + +namespace Msp { +namespace Http { + +Version parse_version(const std::string &ver) +{ + if(RegMatch match=Regex("^HTTP/([0-9]+).([0-9]+)$").match(ver)) + return lexical_cast(match[1].str)<<4 | lexical_cast(match[2].str); + else + throw InvalidParameterValue("Invalid HTTP version"); +} + +string version_str(Version ver) +{ + return format("HTTP/%u.%u", (ver>>4)&0xF, ver&0xF); +} + +} // namespace Http +} // namespace Msp diff --git a/source/misc.h b/source/misc.h new file mode 100644 index 0000000..8e7ed79 --- /dev/null +++ b/source/misc.h @@ -0,0 +1,26 @@ +/* $Id$ + +This file is part of libmsphttp +Copyright © 2008 Mikkosoft Productions, Mikko Rasa +Distributed under the LGPL +*/ + +#ifndef MSP_HTTP_MISC_H_ +#define MSP_HTTP_MISC_H_ + +#include +#include + +namespace Msp { +namespace Http { + +typedef std::map HeaderMap; +typedef unsigned Version; + +Version parse_version(const std::string &); +std::string version_str(Version); + +} // namespace Http +} // namespace Msp + +#endif diff --git a/source/request.cpp b/source/request.cpp new file mode 100644 index 0000000..e63dc66 --- /dev/null +++ b/source/request.cpp @@ -0,0 +1,54 @@ +/* $Id$ + +This file is part of libmsphttp +Copyright © 2008 Mikkosoft Productions, Mikko Rasa +Distributed under the LGPL +*/ + +#include +#include +#include +#include "request.h" + +using namespace std; + +namespace Msp { +namespace Http { + +Request::Request(const string &m, const string &p): + method(m), + path(p) +{ } + +string Request::str() const +{ + string result=format("%s %s %s\r\n", method, path, version_str(http_version)); + result+=str_common(); + + return result; +} + +Request Request::from_url(const string &url) +{ + if(RegMatch match=Regex("^http://([a-zA-Z0-9.-]+)(:([0-9]+))?(/[^ #]*)?$").match(url)) + { + string host=match[1].str; + string port="80"; + if(match[3]) + port=match[3].str; + string path=match[4].str; + if(path.empty()) + path="/"; + + Request result("GET", path); + result.set_header("host", host); + result.set_header("x-port", port); + + return result; + } + else + throw InvalidParameterValue("Invalid URL"); +} + +} // namespace Http +} // namespace Msp diff --git a/source/request.h b/source/request.h new file mode 100644 index 0000000..6f713ab --- /dev/null +++ b/source/request.h @@ -0,0 +1,33 @@ +/* $Id$ + +This file is part of libmsphttp +Copyright © 2008 Mikkosoft Productions, Mikko Rasa +Distributed under the LGPL +*/ + +#ifndef MSP_HTTP_REQUEST_H_ +#define MSP_HTTP_REQUEST_H_ + +#include +#include "message.h" + +namespace Msp { +namespace Http { + +class Request: public Message +{ +public: + Request(const std::string &, const std::string &); + virtual std::string str() const; + + static Request parse(const std::string &); + static Request from_url(const std::string &); +private: + std::string method; + std::string path; +}; + +} // namespace Http +} // namespace Msp + +#endif diff --git a/source/response.cpp b/source/response.cpp new file mode 100644 index 0000000..86d06ba --- /dev/null +++ b/source/response.cpp @@ -0,0 +1,65 @@ +/* $Id$ + +This file is part of libmsphttp +Copyright © 2008 Mikkosoft Productions, Mikko Rasa +Distributed under the LGPL +*/ + +#include +#include +#include "response.h" + +using namespace std; + +namespace Msp { +namespace Http { + +Response::Response(Status s): + status(s) +{ } + +string Response::str() const +{ + string result=format("%s %d %s\r\n", version_str(http_version), int(status), status); + result+=str_common(); + + return result; +} + +Response Response::parse(const string &str) +{ + Response result; + + unsigned lf=str.find('\n'); + vector parts=split(str.substr(0, lf), ' '); + if(parts.size()<2) + throw InvalidParameterValue("Invalid response"); + + result.http_version=parse_version(parts[0]); + result.status=static_cast(lexical_cast(parts[1])); + + unsigned start=lf+1; + while(1) + { + lf=str.find('\n', start); + if(lf==string::npos) + throw InvalidParameterValue("Incomplete response"); + if(lf==start || (str[start]=='\r' && lf==start+1)) + break; + + unsigned colon=str.find(':', start); + if(colon>lf) + throw InvalidParameterValue("No colon in header"); + + result.set_header(str.substr(start, colon-start), strip(str.substr(colon+1, lf-colon-1))); + + start=lf+1; + } + + result.parse_data(str.substr(lf+1)); + + return result; +} + +} // namespace Http +} // namespace Msp diff --git a/source/response.h b/source/response.h new file mode 100644 index 0000000..20e144e --- /dev/null +++ b/source/response.h @@ -0,0 +1,34 @@ +/* $Id$ + +This file is part of libmsphttp +Copyright © 2008 Mikkosoft Productions, Mikko Rasa +Distributed under the LGPL +*/ + +#ifndef MSP_HTTP_RESPONSE_H_ +#define MSP_HTTO_RESPONSE_H_ + +#include "message.h" +#include "status.h" + +namespace Msp { +namespace Http { + +class Response: public Message +{ +public: + Response(Status); + Status get_status() const { return status; } + virtual std::string str() const; + + static Response parse(const std::string &); +private: + Status status; + + Response() { } +}; + +} // namespace Http +} // namespace Msp + +#endif diff --git a/source/status.cpp b/source/status.cpp new file mode 100644 index 0000000..0eb25f3 --- /dev/null +++ b/source/status.cpp @@ -0,0 +1,33 @@ +/* $Id$ + +This file is part of libmsphttp +Copyright © 2008 Mikkosoft Productions, Mikko Rasa +Distributed under the LGPL +*/ + +#include "status.h" + +using namespace std; + +namespace Msp { +namespace Http { + +ostream &operator<<(ostream &out, Status status) +{ + switch(status) + { + case None: out<<"None"; break; + case OK: out<<"OK"; break; + case BadRequest: out<<"Bad Request"; break; + case Forbidden: out<<"Forbidden"; break; + case NotFound: out<<"Not Found"; break; + case InternalError: out<<"Internal Error"; break; + case NotImplemented: out<<"Not Implemented"; break; + default: out<<"Unknown Status"; break; + } + + return out; +} + +} // namespace Http +} // namespace Msp diff --git a/source/status.h b/source/status.h new file mode 100644 index 0000000..5d2b196 --- /dev/null +++ b/source/status.h @@ -0,0 +1,32 @@ +/* $Id$ + +This file is part of libmsphttp +Copyright © 2008 Mikkosoft Productions, Mikko Rasa +Distributed under the LGPL +*/ + +#ifndef MSP_HTTPSERVER_STATUS_H_ +#define MSP_HTTPSERVER_STATUS_H_ + +#include + +namespace Msp { +namespace Http { + +enum Status +{ + None=0, + OK=200, + BadRequest=400, + Forbidden=403, + NotFound=404, + InternalError=500, + NotImplemented=501 +}; + +extern std::ostream &operator<<(std::ostream &, Status); + +} // namespace Http +} // namespace Msp + +#endif