]> git.tdb.fi Git - libs/net.git/commitdiff
Add Server class
authorMikko Rasa <tdb@tdb.fi>
Thu, 25 Dec 2008 08:44:19 +0000 (08:44 +0000)
committerMikko Rasa <tdb@tdb.fi>
Thu, 25 Dec 2008 08:44:19 +0000 (08:44 +0000)
Get rid of the X-Port header and put port in Host, as per HTTP spec
Support for User-Agent header in Client
Move header parsing to Message
Normalize header names to make each word start with uppercase letter

source/client.cpp
source/client.h
source/message.cpp
source/message.h
source/request.cpp
source/request.h
source/response.cpp
source/server.cpp [new file with mode: 0644]
source/server.h [new file with mode: 0644]

index a4bc342f38c2738c27047f6a80818196fbd17396..be579184fa98e62a71d7ea3d09d5c247907fa16f 100644 (file)
@@ -15,14 +15,13 @@ Distributed under the LGPL
 
 using namespace std;
 
-#include <iostream>
-
 namespace Msp {
 namespace Http {
 
 Client::Client():
        sock(0),
        event_disp(0),
+       user_agent("libmsphttp/0.1"),
        request(0),
        response(0)
 { }
@@ -48,7 +47,10 @@ void Client::start_request(const Request &r)
        if(request)
                throw InvalidState("Already processing a request");
 
-       RefPtr<Net::SockAddr> addr=Net::resolve(r.get_header("host")+":"+r.get_header("x-port"));
+       string host=r.get_header("Host");
+       if(host.find(':')==string::npos)
+               host+=":80";
+       RefPtr<Net::SockAddr> addr=Net::resolve(host);
 
        delete sock;
        sock=new Net::StreamSocket(addr->get_family());
@@ -62,6 +64,8 @@ void Client::start_request(const Request &r)
        sock->connect(*addr);
 
        request=new Request(r);
+       if(!user_agent.empty())
+               request->set_header("User-Agent", user_agent);
 
        delete response;
        response=0;
@@ -83,7 +87,7 @@ void Client::tick()
        while(IO::PollEvent ev=IO::poll(*sock, sock->get_events(), Time::zero))
                sock->event(ev);
 
-       if(response && response->get_complete())
+       if(response && response->is_complete())
        {
                signal_response_complete.emit(*response);
 
@@ -96,7 +100,7 @@ void Client::tick()
 
 void Client::wait_response()
 {
-       while(!response || !response->get_complete())
+       while(request && (!response || !response->is_complete()))
                tick();
 }
 
@@ -143,7 +147,7 @@ void Client::data_available()
                in_buf.erase(0, len);
        }
 
-       if(response && response->get_complete())
+       if(response && response->is_complete())
        {
                signal_response_complete.emit(*response);
 
index 2554ac432c7cbed367313f170313fc559afd0956..8c084cec49e45dd655fa895b00573e2fd8bf205b 100644 (file)
@@ -28,6 +28,7 @@ public:
 private:
        Net::StreamSocket *sock;
        IO::EventDispatcher *event_disp;
+       std::string user_agent;
        Request *request;
        Response *response;
        std::string in_buf;
@@ -39,6 +40,7 @@ public:
        ~Client();
 
        void use_event_dispatcher(IO::EventDispatcher *);
+       void set_user_agent(const std::string &);
        void start_request(const Request &);
        const Response *get_url(const std::string &);
        void tick();
index e0a5e7e171bf8afad01cf8932e4ca01acfb720b1..93937c90a71dff3ee8fa65a2fdf973f37e515525 100644 (file)
@@ -12,21 +12,25 @@ Distributed under the LGPL
 
 using namespace std;
 
-#include <iostream>
-
 namespace Msp {
 namespace Http {
 
+Message::Message():
+       http_version(0x11),
+       chunk_length(0),
+       complete(false)
+{ }
+
 void Message::set_header(const string &hdr, const string &val)
 {
-       headers[tolower(hdr)]=val;
+       headers[normalize_header_name(hdr)]=val;
 }
 
 const string &Message::get_header(const string &hdr) const
 {
-       HeaderMap::const_iterator i=headers.find(hdr);
+       HeaderMap::const_iterator i=headers.find(normalize_header_name(hdr));
        if(i==headers.end())
-               throw KeyError(format("Header %s is not defined", hdr));
+               throw KeyError("Undefined header", hdr);
 
        return i->second;
 }
@@ -34,9 +38,9 @@ const string &Message::get_header(const string &hdr) const
 void Message::add_content(const string &d)
 {
        content+=d;
-       if(headers.count("content-type")==0)
-               set_header("content-type", "text/plain");
-       set_header("content-length", lexical_cast(content.size()));
+       if(headers.count("Content-Type")==0)
+               set_header("Content-Type", "text/plain");
+       set_header("Content-Length", lexical_cast(content.size()));
 }
 
 void Message::set_user_data(const Variant &d)
@@ -49,7 +53,7 @@ unsigned Message::parse_content(const string &d)
        if(complete)
                return 0;
 
-       HeaderMap::const_iterator i=headers.find("content-length");
+       HeaderMap::const_iterator i=headers.find("Content-Length");
        if(i!=headers.end())
        {
                unsigned needed=lexical_cast<unsigned>(i->second)-content.size();
@@ -63,7 +67,7 @@ unsigned Message::parse_content(const string &d)
                return len;
        }
 
-       i=headers.find("transfer-encoding");
+       i=headers.find("Transfer-Encoding");
        if(i!=headers.end() && strcasecmp(i->second, "chunked")==0)
        {
                unsigned pos=0;
@@ -92,26 +96,62 @@ unsigned Message::parse_content(const string &d)
                return pos;
        }
 
+       complete=true;
        return 0;
 }
 
-Message::Message():
-       http_version(0x11),
-       chunk_length(0),
-       complete(false)
-{ }
+unsigned Message::parse_headers(const string &d)
+{
+       unsigned start=0;
+       while(1)
+       {
+               unsigned lf=d.find('\n', start);
+               if(lf==string::npos)
+                       throw InvalidParameterValue("Incomplete response");
+               if(lf==start || (d[start]=='\r' && lf==start+1))
+                       return lf+1;
+
+               unsigned colon=d.find(':', start);
+               if(colon>lf)
+                       throw InvalidParameterValue("No colon in header");
+
+               set_header(d.substr(start, colon-start), strip(d.substr(colon+1, lf-colon-1)));
+
+               start=lf+1;
+       }
+}
 
 string Message::str_common() const
 {
        string result;
 
        for(HeaderMap::const_iterator i=headers.begin(); i!=headers.end(); ++i)
-               result+=format("%s: %s\r\n", i->first, i->second);
+               if(i->first[0]!='-')
+                       result+=format("%s: %s\r\n", i->first, i->second);
        result+="\r\n";
        result+=content;
 
        return result;
 }
 
+string Message::normalize_header_name(const string &hdr) const
+{
+       string result=hdr;
+       bool upper=true;
+       for(string::iterator i=result.begin(); i!=result.end(); ++i)
+       {
+               if(upper)
+               {
+                       *i=toupper(*i);
+                       upper=false;
+               }
+               else if(*i=='-')
+                       upper=true;
+               else
+                       *i=tolower(*i);
+       }
+       return result;
+}
+
 } // namespace Http
 } // namespace Msp
index 1ccc976ac71c76c3a8517e181a79ac2ab54f27c6..ae38ffafd9a65dbc1bda16f838da41cb3efa1664 100644 (file)
@@ -38,11 +38,13 @@ public:
        const std::string &get_content() const { return content; }
        void set_user_data(const Variant &);
        const Variant &get_user_data() const { return user_data; }
-       bool get_complete() const { return complete; }
+       bool is_complete() const { return complete; }
        unsigned parse_content(const std::string &);
        virtual std::string str() const =0;
 protected:
+       unsigned parse_headers(const std::string &);
        std::string str_common() const;
+       std::string normalize_header_name(const std::string &) const;
 };
 
 } // namespace Http
index e63dc66f7487e08803380a6f82755528733c52ab..bcf36c991f727f5c539a6431304d68691dd5be92 100644 (file)
@@ -28,21 +28,35 @@ string Request::str() const
        return result;
 }
 
+Request Request::parse(const string &str)
+{
+       unsigned lf=str.find('\n');
+       vector<string> parts=split(str.substr(0, lf-(str[lf-1]=='\r')), ' ', 2);
+       if(parts.size()<3)
+               throw InvalidParameterValue("Invalid request");
+
+       Request result(parts[0], parts[1]);
+       result.http_version=parse_version(parts[2]);
+
+       lf+=result.parse_headers(str.substr(lf+1));
+
+       result.parse_content(str.substr(lf+1));
+
+       return result;
+}
+
 Request Request::from_url(const string &url)
 {
-       if(RegMatch match=Regex("^http://([a-zA-Z0-9.-]+)(:([0-9]+))?(/[^ #]*)?$").match(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;
+               string path=match[3].str;
                if(path.empty())
                        path="/";
 
                Request result("GET", path);
-               result.set_header("host", host);
-               result.set_header("x-port", port);
+               result.set_header("Host", host);
+               result.set_header("Connection", "close");
 
                return result;
        }
index 26d668f95f5d931f9a8b36a1b7f78722d6f35b59..b04fb7e9b7500886885d90888a6beadcd0b8f56f 100644 (file)
@@ -22,6 +22,8 @@ private:
 
 public:
        Request(const std::string &, const std::string &);
+       const std::string &get_method() const { return method; }
+       const std::string &get_path() const { return path; }
        virtual std::string str() const;
 
        static Request parse(const std::string &);
index 0defebd5afb12a7fe3297649bd65b428e5bef22a..4da67422fa221735ea234f64d7430e0d2e857d88 100644 (file)
@@ -20,7 +20,7 @@ Response::Response(Status s):
 
 string Response::str() const
 {
-       string result=format("%s %d %s\r\n", version_str(http_version), int(status), status);
+       string result=format("%s %d %s\r\n", version_str(http_version), static_cast<int>(status), status);
        result+=str_common();
 
        return result;
@@ -31,30 +31,14 @@ Response Response::parse(const string &str)
        Response result;
 
        unsigned lf=str.find('\n');
-       vector<string> parts=split(str.substr(0, lf), ' ');
+       vector<string> parts=split(str.substr(0, lf), ' ', 2);
        if(parts.size()<2)
                throw InvalidParameterValue("Invalid response");
 
        result.http_version=parse_version(parts[0]);
        result.status=static_cast<Status>(lexical_cast<unsigned>(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;
-       }
+       lf+=result.parse_headers(str.substr(lf+1));
 
        result.parse_content(str.substr(lf+1));
 
diff --git a/source/server.cpp b/source/server.cpp
new file mode 100644 (file)
index 0000000..7f159ac
--- /dev/null
@@ -0,0 +1,142 @@
+/* $Id$
+
+This file is part of libmsphttp
+Copyright © 2008  Mikkosoft Productions, Mikko Rasa
+Distributed under the LGPL
+*/
+
+#include <exception>
+#include <msp/core/refptr.h>
+#include <msp/net/inet.h>
+#include <msp/net/streamsocket.h>
+#include "request.h"
+#include "response.h"
+#include "server.h"
+
+using namespace std;
+
+namespace Msp {
+namespace Http {
+
+Server::Server(unsigned port):
+       sock(Net::INET),
+       event_disp(0)
+{
+       sock.signal_data_available.connect(sigc::mem_fun(this, &Server::data_available));
+       sock.listen(Net::InetAddr(0, port), 8);
+}
+
+void Server::use_event_dispatcher(IO::EventDispatcher *ed)
+{
+       if(event_disp)
+       {
+               event_disp->remove(sock);
+               for(list<Client>::iterator i=clients.begin(); i!=clients.end(); ++i)
+                       event_disp->remove(*i->sock);
+       }
+       event_disp=ed;
+       if(event_disp)
+       {
+               event_disp->add(sock);
+               for(list<Client>::iterator i=clients.begin(); i!=clients.end(); ++i)
+                       event_disp->add(*i->sock);
+       }
+}
+
+void Server::data_available()
+{
+       Net::StreamSocket *csock=sock.accept();
+       clients.push_back(Client(csock));
+       csock->signal_data_available.connect(sigc::bind(sigc::mem_fun(this, &Server::client_data_available), sigc::ref(clients.back())));
+       csock->signal_end_of_file.connect(sigc::bind(sigc::mem_fun(this, &Server::client_end_of_file), sigc::ref(clients.back())));
+       if(event_disp)
+               event_disp->add(*csock);
+}
+
+void Server::client_data_available(Client &cl)
+{
+       for(list<Client>::iterator i=clients.begin(); i!=clients.end(); ++i)
+               if(i->stale && &*i!=&cl)
+               {
+                       clients.erase(i);
+                       break;
+               }
+
+       char rbuf[4096];
+       unsigned len=cl.sock->read(rbuf, sizeof(rbuf));
+       cl.in_buf.append(rbuf, len);
+
+       RefPtr<Response> response;
+       if(!cl.request)
+       {
+               if(cl.in_buf.find("\r\n\r\n")!=string::npos || cl.in_buf.find("\n\n")!=string::npos)
+               {
+                       try
+                       {
+                               cl.request=new Request(Request::parse(cl.in_buf));
+                               if(cl.request->get_method()!="GET")
+                               {
+                                       response=new Response(NOT_IMPLEMENTED);
+                                       response->add_content("Method not implemented");
+                               }
+                       }
+                       catch(const exception &e)
+                       {
+                               response=new Response(BAD_REQUEST);
+                               response->add_content(e.what());
+                       }
+                       cl.in_buf=string();
+               }
+       }
+       else
+       {
+               len=cl.request->parse_content(cl.in_buf);
+               cl.in_buf.erase(0, len);
+       }
+
+       if(cl.request && cl.request->is_complete() && !response)
+       {
+               response=new Response(NONE);
+               try
+               {
+                       signal_request.emit(*cl.request, *response);
+                       if(response->get_status()==NONE)
+                       {
+                               response=new Response(NOT_FOUND);
+                               response->add_content("The requested resource was not found");
+                       }
+               }
+               catch(const exception &e)
+               {
+                       response=new Response(INTERNAL_ERROR);
+                       response->add_content(e.what());
+               }
+       }
+
+       if(response)
+       {
+               cl.sock->write(response->str());
+               cl.sock->close();
+               cl.stale=true;
+       }
+}
+
+void Server::client_end_of_file(Client &cl)
+{
+       cl.stale=true;
+}
+
+
+Server::Client::Client(RefPtr<Net::StreamSocket> s):
+       sock(s),
+       request(0),
+       stale(false)
+{ }
+
+Server::Client::~Client()
+{
+       delete request;
+}
+
+} // namespace Http
+} // namespace Msp
diff --git a/source/server.h b/source/server.h
new file mode 100644 (file)
index 0000000..ea12134
--- /dev/null
@@ -0,0 +1,54 @@
+/* $Id$
+
+This file is part of libmsphttp
+Copyright © 2008  Mikkosoft Productions, Mikko Rasa
+Distributed under the LGPL
+*/
+
+#ifndef MSP_HTTP_SERVER_H_
+#define MSP_HTTP_SERVER_H_
+
+#include <msp/core/refptr.h>
+#include <msp/io/eventdispatcher.h>
+#include <msp/net/streamlistensocket.h>
+
+namespace Msp {
+namespace Http {
+
+class Request;
+class Response;
+
+class Server
+{
+public:
+       sigc::signal<void, const Request &, Response &> signal_request;
+
+private:
+       struct Client
+       {
+               RefPtr<Net::StreamSocket> sock;
+               std::string in_buf;
+               Request *request;
+               bool stale;
+
+               Client(RefPtr<Net::StreamSocket>);
+               ~Client();
+       };
+
+       Net::StreamListenSocket sock;
+       std::list<Client> clients;
+       IO::EventDispatcher *event_disp;
+
+public:
+       Server(unsigned);
+       void use_event_dispatcher(IO::EventDispatcher *);
+private:
+       void data_available();
+       void client_data_available(Client &);
+       void client_end_of_file(Client &);
+};
+
+} // namespace Http
+} // namespace Msp
+
+#endif