社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
最近写了点关于Http上传下载文件相关的,于是今天整理下代码。
Http协议简述
HttpRequest类设计
使用示例
协议
:网络协议的简称,网络协议是通信计算机双方必须共同遵从的一组约定。如怎么样建立连接、怎么样互相识别等。只有遵守这个约定,计算机之间才能相互通信交流,就好比两台计算机交互的语言.
HTTP协议
:超文本传输协议(HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。主要被用于在Web浏览器和网站服务器之间传递信息。 HTTP 是基于 TCP/IP 协议的应用层协议。默认使用80端口。最新版本是HTTP 2.0,目前是用最广泛的是HTTP 1.1。
HTTP协议工作于客户端-服务端架构为上。浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。Web服务器根据接收到的请求后,向客户端发送响应信息。
请求方法:
HTTP/1.1协议中共定义了八种方法(有时也叫“动作”)来表明Request-URI指定的资源的不同操作方式:OPTIONS
- 返回服务器针对特定资源所支持的HTTP请求方法。也可以利用向Web服务器发送’*'的请求来测试服务器的功能性。HEAD
- 向服务器索要与GET请求相一致的响应,只不过响应体将不会被返回。这一方法可以在不必传输整个响应内容的情况下,就可以获取包含在响应消息头中的元信息。该方法常用于测试超链接的有效性,是否可以访问,以及最近是否更新。GET
- 向特定的资源发出请求。注意:GET方法不应当被用于产生“副作用”的操作中,例如在web app.中。其中一个原因是GET可能会被网络蜘蛛等随意访问。POST
- 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。PUT
- 向指定资源位置上传其最新内容。DELETE
- 请求服务器删除Request-URI所标识的资源。TRACE
- 回显服务器收到的请求,主要用于测试或诊断。CONNECT
- HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。PATCH
- 用来将局部修改应用于某一资源,添加于规范RFC5789。
方法名称是区分大小写的。当某个请求所针对的资源不支持对应的请求方法的时候,服务器应当返回状态码405(Method Not Allowed);当服务器不认识或者不支持对应的请求方法的时候,应当返回状态码501(Not Implemented)。
HTTP服务器至少应该实现GET和HEAD方法,其他方法都是可选的。此外,除了上述方法,特定的HTTP服务器还能够扩展自定义的方法。
GET和POST请求的区别
GET请求
GET /books/?sex=man&name=Professional HTTP/1.1
Host: www.wrox.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
Gecko/20050225 Firefox/1.0.1
Connection: Keep-Alive
注意最后有一行空行
POST请求
POST / HTTP/1.1
Host: www.wrox.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
Gecko/20050225 Firefox/1.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 40
Connection: Keep-Alive
name=Professional%20Ajax&publisher=Wiley
1、
GET提交
:请求的数据会附在URL之后(就是把数据放置在HTTP协议头中),以?分割URL和传输数据,多个参数用&连接;例 如:login.action?name=hyddd&password=idontknow&verify=%E4%BD%A0 %E5%A5%BD。如果数据是英文字母/数字,原样发送,如果是空格,转换为+,如果是中文/其他字符,则直接把字符串用BASE64加密,得出如: %E4%BD%A0%E5%A5%BD,其中%XX中的XX为该符号以16进制表示的ASCII。
2、
POST提交
:把提交的数据放置在是HTTP包的包体中。上文示例中红色字体标明的就是实际的传输数据
HttpRequest::HttpRequest() 通过传入Url构造HttpUrl类分离url域名及uri.
HttpRequest::connect() 通过gethostbyname()获取域名ip,与80端口组成远端地址建立链接.
HttpRequest::setRequestMethod() 设置请求方法,目前只添加了Get和Post请求.
HttpRequest::setRequestProperty() 设置属性.
HttpRequest::setRequestBody() 设置content.
HttpRequest::send() 将设置的请求流发送出去.
HttpRequest::handRead() 处理服务器应答头.
HttpRequest::getResponseCode() handRead()后可以获取到应答code
HttpRequest::getResponseProperty() handRead()后可以获取到对应的应答属性
HttpRequest::getResponseContent() handRead()后可以获取到应答content
#ifndef _HTTP_REQUEST_HH
#define _HTTP_REQUEST_HH
#include <netdb.h>
#include <arpa/inet.h>
#include <algorithm>
#include <vector>
#include <string>
#include <assert.h>
#include <string.h>
#include <sstream>
#include <map>
#include "Logger.hh"
const size_t kBufferSize = 4096;
/// A buffer class modeled after org.jboss.netty.buffer.ChannelBuffer
///
/// @code
/// +-------------------+------------------+------------------+
/// | prependable bytes | readable bytes | writable bytes |
/// | | (CONTENT) | |
/// +-------------------+------------------+------------------+
/// | | | |
/// 0 <= readerIndex <= writerIndex <= size
/// @endcode
class Buffer
{
public:
static const size_t kCheapPrepend = 8;
static const size_t kInitialSize = 4096;
//public:
explicit Buffer(size_t initialSize = kInitialSize)
: m_buffer(kCheapPrepend + initialSize),
m_readerIndex(kCheapPrepend),
m_writerIndex(kCheapPrepend)
{
assert(readableBytes() == 0);
assert(writableBytes() == initialSize);
assert(prependableBytes() == kCheapPrepend);
}
size_t readableBytes() const
{ return m_writerIndex - m_readerIndex; }
size_t writableBytes() const
{ return m_buffer.size() - m_writerIndex; }
size_t prependableBytes() const
{ return m_readerIndex; }
const char* peek() const
{ return begin() + m_readerIndex; }
char* beginWrite()
{ return begin() + m_writerIndex; }
void hasWritten(size_t len)
{
assert(len <= writableBytes());
m_writerIndex += len;
}
void unwrite(size_t len)
{
assert(len <= readableBytes());
m_writerIndex -= len;
}
// retrieve returns void, to prevent
// string str(retrieve(readableBytes()), readableBytes());
// the evaluation of two functions are unspecified
void retrieve(size_t len)
{
assert(len <= readableBytes());
if (len < readableBytes())
{
m_readerIndex += len;
}
else
{
retrieveAll();
}
}
void retrieveAll()
{
m_readerIndex = kCheapPrepend;
m_writerIndex = kCheapPrepend;
}
private:
char* begin()
{return &*m_buffer.begin(); }
const char* begin() const
{return &*m_buffer.begin(); }
private:
std::vector<char> m_buffer;
size_t m_readerIndex;
size_t m_writerIndex;
};
namespace sockets
{
/// Creates a non-blocking socket file descriptor,
/// abort if any error.
int createSocket(sa_family_t family);
int connect(int sockfd, const struct sockaddr* addr);
ssize_t read(int sockfd, void *buf, size_t count);
ssize_t readv(int sockfd, const struct iovec *iov, int iovcnt);
ssize_t write(int sockfd, const void *buf, size_t count);
void close(int sockfd);
void fromIpPort(const char* ip, uint16_t port,
struct sockaddr_in* addr);
int getSocketError(int sockfd);
void delaySecond(int sec);
//const struct sockaddr* sockaddr_cast(const struct sockaddr_in* addr)
//const struct sockaddr_in* sockaddr_in_cast(const struct sockaddr* addr);
}
class InetAddress
{
public:
/// Constructs an endpoint with given ip and port.
/// @c ip should be "1.2.3.4"
InetAddress(std::string ip, uint16_t port);
/// Constructs an endpoint with given struct @c sockaddr_in
/// Mostly used when accepting new connections
explicit InetAddress(const struct sockaddr_in& addr)
: m_addr(addr)
{ }
sa_family_t family() const { return m_addr.sin_family; }
//std::string toIp() const;
//std::string toIpPort() const;
const struct sockaddr* getSockAddr() const { return (struct sockaddr*)(&m_addr); }
uint32_t ipNetEndian() const;
// resolve hostname to IP address, not changing port or sin_family
// return true on success.
// thread safe
// static bool resolve(StringArg hostname, StringArg* ip);
// static std::vector<InetAddress> resolveAll(const char* hostname, uint16_t port = 0);
private:
struct sockaddr_in m_addr;
};
class HttpUrl
{
public:
HttpUrl(std::string& httpUrl)
:m_httpUrl(httpUrl),
m_smatch(detachHttpUrl())
{
LOG_DEBUG << "URL : " << m_httpUrl;
}
~HttpUrl(){};
enum HttpUrlMatch
{
URL = 0,
HOST = 1,
URI = 2
};
std::vector<std::string> detachHttpUrl() const
{
std::vector<std::string> v;
std::string::size_type pos1, pos2;
pos2 = m_httpUrl.find('/');
assert(std::string::npos != pos2);
pos1 = pos2 + 2;
pos2 = m_httpUrl.find('/', pos1);
assert(std::string::npos != pos2);
v.push_back(m_httpUrl);
v.push_back(m_httpUrl.substr(pos1, pos2 - pos1));
v.push_back(m_httpUrl.substr(pos2 + 1));
LOG_DEBUG << "detachHttpUrl() url :" << v[0];
LOG_DEBUG << "detachHttpUrl() host :" << v[1];
LOG_DEBUG << "detachHttpUrl() uri :" << v[2];
return v;
}
bool HttpUrlToIp(const std::string& host, char* ip) const
{
struct hostent* phost = NULL;
phost = gethostbyname(host.c_str());
if (NULL == phost)
{
LOG_ERROR << "HttpUrlToIp(): gethostbyname error : " << errno << " : "<< strerror(errno);
return false;
//LOG_SYSERR << "urlToIp(): gethostbyname error";
}
inet_ntop(phost->h_addrtype, phost->h_addr, ip, 17);
return true;
}
std::string domain() const
{
return getHttpUrlSubSeg(HOST);
}
std::string getHttpUrlSubSeg(HttpUrlMatch sub = HOST) const{ return m_smatch[sub]; }
private:
std::string m_httpUrl;
std::vector<std::string> m_smatch;
};
class HttpRequest
{
public:
enum HttpRequestMethod{
GET = 0,
POST
};
HttpRequest(std::string httpUrl);
~HttpRequest();
void connect();
//void TEST(const std::string path,const std::string content);
void setRequestMethod(const std::string &method);
void setRequestProperty(const std::string &key, const std::string &value);
void setRequestBody(const std::string &content);
//void clear() { clearStream(); m_buffer.retrieveAll(); }
void clearStream() {m_stream.str("");}
std::string strStream() const { return m_stream.str(); };
int getResponseCode() const {
assert(m_haveHandleHead);
return m_code;
}
std::string getResponseProperty(const std::string& key) const {
assert(m_haveHandleHead);
return m_ackProperty.at(key);
}
std::string getResponseContent() {
assert(m_haveHandleHead);
return std::string(m_buffer.peek(), m_buffer.readableBytes());
}
void handleRead();
void uploadFile(const std::string& file, const std::string& contentEnd);
void downloadFile(const std::string& file);
void send(){
sockets::write(m_sockfd, strStream().c_str(), strStream().size());
}
void close(){ sockets::close(m_sockfd); }
private:
void SplitString(const std::string& s, std::vector<std::string>& v, const std::string& c);
Buffer m_buffer;
HttpUrl m_httpUrl;
std::stringstream m_stream;
int m_code;
int m_sockfd;
bool m_haveHandleHead;
std::map<std::string, std::string> m_ackProperty;
};
#endif
HttpRequest::downloadFile(const std::string& file);
下载直接在 handRead() 处理完应答头后 , 调用downloadFile() 存储在本地.
HttpRequest::uploadFile(const std::string& file, const std::string& contentEnd)
上传部分复杂一点,需要根据kBoundary 设置边界,如下.
off_t fileSize = FileSize(uploadFile);
LOG_DEBUG << "fileSize : " << fileSize;
std::stringstream content;
content << "--" + kBoundary << "rn";
content << "Content-Disposition: form-data; name="upload"; filename="test.file"rn";
content << "Content-Type: text/plainrnrn";
std::string contentEnd = "rn--" + kBoundary + "--rn";
HttpRequest upload("http://xxxxx/upload");
upload.setRequestMethod("POST");
upload.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + kBoundary);
upload.setRequestProperty("Cache-Control", "no-cache");
upload.setRequestProperty("Content-Length", std::to_string(fileSize + content.str().size() + contentEnd.size()));
upload.setRequestProperty("Connection", "closern");
upload.setRequestBody(content.str());
#include <string>
#include <iostream>
#include <fcntl.h>
#include <errno.h>
#include <sys/uio.h> // readv
#include <stdint.h>
#include <endian.h>
#include <unistd.h>
#include <map>
#include <fstream>
#include "HttpRequest.hh"
const std::map<std::string, int>::value_type init_value[] =
{
std::map<std::string, int>::value_type( "GET", HttpRequest::GET),
std::map<std::string, int>::value_type( "POST", HttpRequest::POST)
};
const static std::map<std::string, int> kRequestMethodMap(init_value, init_value + (sizeof init_value / sizeof init_value[0]));
static inline uint16_t hostToNetwork16(uint16_t host16)
{
return htobe16(host16);
}
int sockets::createSocket(sa_family_t family){
// Call "socket()" to create a (family) socket of the specified type.
// But also set it to have the 'close on exec' property (if we can)
int sock;
//CLOEXEC,即当调用exec()函数成功后,文件描述符会自动关闭。
//在以往的内核版本(2.6.23以前)中,需要调用 fcntl(fd, F_SETFD, FD_CLOEXEC) 来设置这个属性。
//而新版本(2.6.23开始)中,可以在调用open函数的时候,通过 flags 参数设置 CLOEXEC 功能,
#ifdef SOCK_CLOEXEC
sock = socket(family, SOCK_STREAM|SOCK_CLOEXEC, 0);
if (sock != -1 || errno != EINVAL) return sock;
// An "errno" of EINVAL likely means that the system wasn't happy with the SOCK_CLOEXEC; fall through and try again without it:
#endif
sock = socket(family, SOCK_STREAM, 0);
#ifdef FD_CLOEXEC
if (sock != -1) fcntl(sock, F_SETFD, FD_CLOEXEC);
#endif
return sock;
}
int sockets::connect(int sockfd, const struct sockaddr* addr)
{
return ::connect(sockfd, addr, sizeof(struct sockaddr));
}
void sockets::fromIpPort(const char* ip, uint16_t port,
struct sockaddr_in* addr)
{
addr->sin_family = AF_INET;
a
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!