Added support for sending and receiving HTTP requests.
Added 'HttpSocket', which inherits TcpSocket and adds a receive and send functions for WebRequest objects. WebRequest objects can be used to parse, construct and extract HTTP requests. They can be sent through HttpSocket's.
This commit is contained in:
parent
a3eb2ccd0a
commit
7aced77a00
@ -4,5 +4,5 @@ project(frnetlib)
|
|||||||
include_directories(include)
|
include_directories(include)
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -m64 -fPIC -pthread")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -m64 -fPIC -pthread")
|
||||||
|
|
||||||
set(SOURCE_FILES main.cpp src/TcpSocket.cpp include/TcpSocket.h src/TcpListener.cpp include/TcpListener.h src/Socket.cpp include/Socket.h src/Packet.cpp include/Packet.h include/NetworkEncoding.h src/SocketSelector.cpp include/SocketSelector.h)
|
set(SOURCE_FILES main.cpp src/TcpSocket.cpp include/TcpSocket.h src/TcpListener.cpp include/TcpListener.h src/Socket.cpp include/Socket.h src/Packet.cpp include/Packet.h include/NetworkEncoding.h src/SocketSelector.cpp include/SocketSelector.h src/HttpSocket.cpp include/HttpSocket.h include/HttpRequest.cpp include/HttpRequest.h)
|
||||||
add_executable(frnetlib ${SOURCE_FILES})
|
add_executable(frnetlib ${SOURCE_FILES})
|
||||||
214
include/HttpRequest.cpp
Normal file
214
include/HttpRequest.cpp
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
//
|
||||||
|
// Created by fred on 10/12/16.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "HttpRequest.h"
|
||||||
|
|
||||||
|
namespace fr
|
||||||
|
{
|
||||||
|
HttpRequest::HttpRequest()
|
||||||
|
: status(Ok)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpRequest::parse_request(const std::string &request_data)
|
||||||
|
{
|
||||||
|
//Warning: Horrible string parsing code
|
||||||
|
|
||||||
|
//Clear old headers/data
|
||||||
|
clear();
|
||||||
|
|
||||||
|
//Make sure there's actual request data to read
|
||||||
|
if(request_data.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
//Split by new lines
|
||||||
|
std::vector<std::string> lines = split_string(request_data);
|
||||||
|
if(lines.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
//Extract request type
|
||||||
|
if(lines[0].find("GET") != std::string::npos)
|
||||||
|
request_type = RequestType::Get;
|
||||||
|
else if(lines[0].find("POST") != std::string::npos)
|
||||||
|
request_type = RequestType::Post;
|
||||||
|
else
|
||||||
|
request_type = RequestType::Unknown;
|
||||||
|
|
||||||
|
//Remove HTTP version
|
||||||
|
auto http_version = lines[0].find("HTTP");
|
||||||
|
if(http_version != std::string::npos && http_version > 0)
|
||||||
|
lines[0].erase(http_version - 1, lines[0].size() - http_version + 1);
|
||||||
|
|
||||||
|
//Extract URI & GET variables
|
||||||
|
auto uri_start = lines[0].find(" ");
|
||||||
|
auto uri_end = lines[0].find("?");
|
||||||
|
if(uri_start != std::string::npos)
|
||||||
|
{
|
||||||
|
if(uri_end == std::string::npos) //If no GET arguments
|
||||||
|
{
|
||||||
|
uri = lines[0].substr(uri_start + 1, lines[0].size() - 1);
|
||||||
|
}
|
||||||
|
else //There's get arguments
|
||||||
|
{
|
||||||
|
uri = lines[0].substr(uri_start + 1, uri_end - uri_start - 1);
|
||||||
|
std::string get_lines = lines[0].substr(uri_end + 1, lines[0].size());
|
||||||
|
std::string name_buffer, value_buffer;
|
||||||
|
|
||||||
|
bool state = false;
|
||||||
|
for(size_t a = 0; a < get_lines.size(); a++)
|
||||||
|
{
|
||||||
|
if(get_lines[a] == '&')
|
||||||
|
{
|
||||||
|
get_variables.emplace(name_buffer, value_buffer);
|
||||||
|
name_buffer.clear();
|
||||||
|
value_buffer.clear();
|
||||||
|
state = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if(get_lines[a] == '=')
|
||||||
|
{
|
||||||
|
state = true;
|
||||||
|
}
|
||||||
|
else if(state)
|
||||||
|
{
|
||||||
|
value_buffer += get_lines[a];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
name_buffer += get_lines[a];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get_variables.emplace(name_buffer, value_buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Extract headers
|
||||||
|
size_t a;
|
||||||
|
for(a = 1; a < lines.size(); a++)
|
||||||
|
{
|
||||||
|
//New line indicates headers have ended
|
||||||
|
if(lines[a].empty() || lines[a].size() <= 2)
|
||||||
|
break;
|
||||||
|
|
||||||
|
//Find the colon separating the header name and header data
|
||||||
|
auto colon_iter = lines[a].find(":");
|
||||||
|
if(colon_iter == std::string::npos)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
//Store the header
|
||||||
|
std::string header_name = lines[a].substr(0, colon_iter);
|
||||||
|
std::string header_content = lines[a].substr(colon_iter + 2, lines[a].size () - colon_iter - 3);
|
||||||
|
headers.emplace(header_name, header_content);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Store request body
|
||||||
|
for(; a < lines.size(); a++)
|
||||||
|
{
|
||||||
|
body += lines[a] + "\n";
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpRequest::RequestType HttpRequest::type() const
|
||||||
|
{
|
||||||
|
return request_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string &HttpRequest::operator[](const std::string &key)
|
||||||
|
{
|
||||||
|
return headers[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> HttpRequest::split_string(const std::string &str)
|
||||||
|
{
|
||||||
|
char last_character = '\0';
|
||||||
|
size_t line_start = 0;
|
||||||
|
std::vector<std::string> result;
|
||||||
|
|
||||||
|
for(size_t a = 0; a < str.size(); a++)
|
||||||
|
{
|
||||||
|
if(str[a] == '\n' && last_character != '\\')
|
||||||
|
{
|
||||||
|
result.emplace_back(str.substr(line_start, a - line_start));
|
||||||
|
line_start = a + 1;
|
||||||
|
}
|
||||||
|
last_character = str[a];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string HttpRequest::get_request() const
|
||||||
|
{
|
||||||
|
//Add HTTP header
|
||||||
|
std::string request = "HTTP/1.1 " + std::to_string(status) + " \r\n";
|
||||||
|
|
||||||
|
//Add the headers to the request
|
||||||
|
for(const auto &header : headers)
|
||||||
|
{
|
||||||
|
std::string data = header.first + ": " + header.second + "\r\n";
|
||||||
|
request += data;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add in required headers if they're missing
|
||||||
|
if(headers.find("Connection") == headers.end())
|
||||||
|
request += "Connection: close\r\n";
|
||||||
|
if(headers.find("Content-type") == headers.end())
|
||||||
|
request += "Content-type: text/html\r\n";
|
||||||
|
|
||||||
|
//Add in space
|
||||||
|
request += "\r\n";
|
||||||
|
|
||||||
|
//Add in the body
|
||||||
|
request += body + "\r\n";
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpRequest::set_body(const std::string &body_)
|
||||||
|
{
|
||||||
|
body = body_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpRequest::clear()
|
||||||
|
{
|
||||||
|
headers.clear();
|
||||||
|
body.clear();
|
||||||
|
get_variables.clear();
|
||||||
|
status = Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string &HttpRequest::get(const std::string &key)
|
||||||
|
{
|
||||||
|
return get_variables[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string &HttpRequest::post(const std::string &key)
|
||||||
|
{
|
||||||
|
return headers[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HttpRequest::get_exists(const std::string &key) const
|
||||||
|
{
|
||||||
|
return get_variables.find(key) != get_variables.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HttpRequest::post_exists(const std::string &key) const
|
||||||
|
{
|
||||||
|
return headers.find(key) != headers.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string &HttpRequest::get_uri() const
|
||||||
|
{
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpRequest::set_status(RequestStatus status_)
|
||||||
|
{
|
||||||
|
status = status_;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpRequest::RequestStatus HttpRequest::get_status()
|
||||||
|
{
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
156
include/HttpRequest.h
Normal file
156
include/HttpRequest.h
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
//
|
||||||
|
// Created by fred on 10/12/16.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef FRNETLIB_HTTPREQUEST_H
|
||||||
|
#define FRNETLIB_HTTPREQUEST_H
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include "TcpSocket.h"
|
||||||
|
|
||||||
|
namespace fr
|
||||||
|
{
|
||||||
|
class HttpRequest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum RequestType
|
||||||
|
{
|
||||||
|
Unknown = 0,
|
||||||
|
Get = 1,
|
||||||
|
Post = 2,
|
||||||
|
};
|
||||||
|
enum RequestStatus
|
||||||
|
{
|
||||||
|
Ok = 200,
|
||||||
|
BadRequest = 400,
|
||||||
|
Forbidden = 403,
|
||||||
|
NotFound = 404,
|
||||||
|
ImATeapot = 418,
|
||||||
|
InternalServerError = 500,
|
||||||
|
};
|
||||||
|
|
||||||
|
//Constructors
|
||||||
|
HttpRequest();
|
||||||
|
HttpRequest(HttpRequest &&other)=default;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Parses a browser request
|
||||||
|
*
|
||||||
|
* @param request_data The request data itself
|
||||||
|
*/
|
||||||
|
void parse_request(const std::string &request_data);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Gets the request type (post, get etc)
|
||||||
|
*
|
||||||
|
* @return The request type
|
||||||
|
*/
|
||||||
|
RequestType type() const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Access a header
|
||||||
|
*
|
||||||
|
* @param key The name of the header data to access/create
|
||||||
|
* @return The header data.
|
||||||
|
*/
|
||||||
|
std::string &operator[](const std::string &key);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Constructs a HTTP web request from the object.
|
||||||
|
*
|
||||||
|
* @return The HTTP request
|
||||||
|
*/
|
||||||
|
std::string get_request() const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Sets the request body
|
||||||
|
*
|
||||||
|
* @param body_ The request body
|
||||||
|
*/
|
||||||
|
void set_body(const std::string &body_);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Clear current request data
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Returns a reference to a get variable.
|
||||||
|
* Can be used to either set/get the value.
|
||||||
|
* If the key does not exist, then it will be
|
||||||
|
* created and an empty value will be returned.
|
||||||
|
*
|
||||||
|
* @param key The name of the GET variable
|
||||||
|
* @return A reference to the GET variable
|
||||||
|
*/
|
||||||
|
std::string &get(const std::string &key);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Returns a reference to a POST variable.
|
||||||
|
* Can be used to either set/get the value.
|
||||||
|
* If the key does not exist, then it will be
|
||||||
|
* created and an empty value will be returned.
|
||||||
|
*
|
||||||
|
* @param key The name of the POST variable
|
||||||
|
* @return A reference to the POST variable
|
||||||
|
*/
|
||||||
|
std::string &post(const std::string &key);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Checks to see if a given GET variable exists
|
||||||
|
*
|
||||||
|
* @param key The name of the GET variable
|
||||||
|
* @return True if it does. False otherwise.
|
||||||
|
*/
|
||||||
|
bool get_exists(const std::string &key) const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Checks to see if a given POST variable exists
|
||||||
|
*
|
||||||
|
* @param key The name of the POST variable
|
||||||
|
* @return True if it does. False otherwise.
|
||||||
|
*/
|
||||||
|
bool post_exists(const std::string &key) const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Returns the requested URI
|
||||||
|
*
|
||||||
|
* @return The URI
|
||||||
|
*/
|
||||||
|
const std::string &get_uri() const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Sets the response status (400, 200, etc)
|
||||||
|
*
|
||||||
|
* @param status The status to send back
|
||||||
|
*/
|
||||||
|
void set_status(RequestStatus status);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Gets the reponse status
|
||||||
|
*
|
||||||
|
* @return The status
|
||||||
|
*/
|
||||||
|
RequestStatus get_status();
|
||||||
|
|
||||||
|
private:
|
||||||
|
/*!
|
||||||
|
* Splits a string by new line. Ignores escaped \n's
|
||||||
|
*
|
||||||
|
* @return The split string
|
||||||
|
*/
|
||||||
|
std::vector<std::string> split_string(const std::string &str);
|
||||||
|
|
||||||
|
//Other request info
|
||||||
|
std::unordered_map<std::string, std::string> headers;
|
||||||
|
std::unordered_map<std::string, std::string> get_variables;
|
||||||
|
std::string body;
|
||||||
|
RequestType request_type;
|
||||||
|
std::string uri;
|
||||||
|
RequestStatus status;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif //FRNETLIB_HTTPREQUEST_H
|
||||||
35
include/HttpSocket.h
Normal file
35
include/HttpSocket.h
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
//
|
||||||
|
// Created by fred on 10/12/16.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef FRNETLIB_HTTPSOCKET_H
|
||||||
|
#define FRNETLIB_HTTPSOCKET_H
|
||||||
|
|
||||||
|
#include "TcpSocket.h"
|
||||||
|
#include "HttpRequest.h"
|
||||||
|
|
||||||
|
namespace fr
|
||||||
|
{
|
||||||
|
class HttpSocket : public TcpSocket
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/*!
|
||||||
|
* Sends a HTTP request to the connected socket.
|
||||||
|
*
|
||||||
|
* @param request The request to send
|
||||||
|
* @return The status of the operation.
|
||||||
|
*/
|
||||||
|
Socket::Status receive(HttpRequest &request);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Sends a HTTP request to the connected socket.
|
||||||
|
*
|
||||||
|
* @param request Where to store the received request.
|
||||||
|
* @return The status of the operation.
|
||||||
|
*/
|
||||||
|
Socket::Status send(const HttpRequest &request);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //FRNETLIB_HTTPSOCKET_H
|
||||||
28
main.cpp
28
main.cpp
@ -1,11 +1,12 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <Packet.h>
|
#include "include/Packet.h"
|
||||||
#include <TcpSocket.h>
|
#include "include/TcpSocket.h"
|
||||||
#include <TcpListener.h>
|
#include "include/TcpListener.h"
|
||||||
#include <SocketSelector.h>
|
#include "include/SocketSelector.h"
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <HttpSocket.h>
|
||||||
|
|
||||||
void server()
|
void server()
|
||||||
{
|
{
|
||||||
@ -17,7 +18,7 @@ void server()
|
|||||||
}
|
}
|
||||||
|
|
||||||
fr::SocketSelector selector;
|
fr::SocketSelector selector;
|
||||||
std::vector<std::unique_ptr<fr::TcpSocket>> clients;
|
std::vector<std::unique_ptr<fr::HttpSocket>> clients;
|
||||||
|
|
||||||
selector.add(listener);
|
selector.add(listener);
|
||||||
|
|
||||||
@ -25,7 +26,7 @@ void server()
|
|||||||
{
|
{
|
||||||
if(selector.is_ready(listener))
|
if(selector.is_ready(listener))
|
||||||
{
|
{
|
||||||
clients.emplace_back(new fr::TcpSocket());
|
clients.emplace_back(new fr::HttpSocket());
|
||||||
if(listener.accept(*clients.back()) != fr::Socket::Success)
|
if(listener.accept(*clients.back()) != fr::Socket::Success)
|
||||||
{
|
{
|
||||||
clients.pop_back();
|
clients.pop_back();
|
||||||
@ -41,17 +42,14 @@ void server()
|
|||||||
{
|
{
|
||||||
if(selector.is_ready(**iter))
|
if(selector.is_ready(**iter))
|
||||||
{
|
{
|
||||||
std::string message(1024, '\0');
|
fr::HttpRequest request;
|
||||||
size_t received;
|
if((*iter)->receive(request) == fr::Socket::Success)
|
||||||
if((*iter)->receive_raw(&message[0], 1024, received) == fr::Socket::Success)
|
|
||||||
{
|
{
|
||||||
|
std::cout << "Requested: " << request.get_uri() << std::endl;
|
||||||
|
request.clear();
|
||||||
|
request.set_body("<h1>Hello, World!</h1>");
|
||||||
|
|
||||||
std::cout << (*iter)->get_remote_address() << " sent: " << message.substr(0, received) << std::endl;
|
(*iter)->send(request);
|
||||||
|
|
||||||
message.clear();
|
|
||||||
message = "HTTP/1.1 " + std::to_string(200) + " \r\nConnection: close\r\nContent-type: text/html\r\n\r\n<h1>Hey</h1>\r\n";
|
|
||||||
(*iter)->send_raw(&message[0], message.size());
|
|
||||||
std::cout << "Sent" << std::endl;
|
|
||||||
(*iter)->close();
|
(*iter)->close();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
33
src/HttpSocket.cpp
Normal file
33
src/HttpSocket.cpp
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
//
|
||||||
|
// Created by fred on 10/12/16.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "HttpSocket.h"
|
||||||
|
|
||||||
|
namespace fr
|
||||||
|
{
|
||||||
|
|
||||||
|
Socket::Status HttpSocket::receive(HttpRequest &request)
|
||||||
|
{
|
||||||
|
//Create buffer to receive the request
|
||||||
|
std::string buffer(2048, '\0');
|
||||||
|
|
||||||
|
//Receive the request
|
||||||
|
size_t received;
|
||||||
|
Socket::Status status = receive_raw(&buffer[0], buffer.size(), received);
|
||||||
|
if(status != Socket::Success)
|
||||||
|
return status;
|
||||||
|
buffer.resize(received);
|
||||||
|
|
||||||
|
//Parse it
|
||||||
|
request.parse_request(buffer);
|
||||||
|
|
||||||
|
return Socket::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
Socket::Status HttpSocket::send(const HttpRequest &request)
|
||||||
|
{
|
||||||
|
std::string data = request.get_request();
|
||||||
|
return send_raw(&data[0], data.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -193,6 +193,7 @@ namespace fr
|
|||||||
if(c == nullptr)
|
if(c == nullptr)
|
||||||
return Socket::Status::Error;
|
return Socket::Status::Error;
|
||||||
is_connected = true;
|
is_connected = true;
|
||||||
|
return Socket::Status::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user