搭建web伺服器(tiny web)
伺服器端
客戶端
代碼設置的默認路徑為桌面,hello.txt文件位於桌面,選擇埠的埠號為8000(命令行第二個參數),127.0.0.1代表回送地址,指本地機,需要先行確認可用(本人通過Apache打開)。
#include <iostream>
#include <unistd.h>
#include <cstddef>
#include <sys/socket.h>
#include <netdb.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <string>
//保證robust讀寫
ssize_t rio_writen(int fd, void *userbuf, size_t n)
{
size_t nleft = n;
ssize_t nwrite;
char *bufp = (char*)userbuf;
while(nleft > 0)
{
if((nwrite = write(fd, bufp, nleft))<0)
{
if(errno == EINTR)
nwrite = 0;
else
return -1;
}
nleft -= nwrite;
bufp += nwrite;
}
return n;
}
//帶緩存版本
static const int RIO_BUFSIZE = 8192;
struct rio_t
{
int rio_fd; //文件描述符
int rio_cnt; //未讀位元組數
char *rio_bufptr; //下一個未讀位元組
char rio_buf[RIO_BUFSIZE];//內部緩衝
};
void rio_readinitb(rio_t *rp, int fd)
{
rp->rio_fd = fd;
rp->rio_cnt = 0;
rp->rio_bufptr = rp->rio_buf;
}
ssize_t rio_read(rio_t *rp, void *userbuf, size_t n)
{
while(rp->rio_cnt <= 0)
{
rp->rio_cnt = read(rp->rio_fd, rp->rio_buf, sizeof(rp->rio_buf));
if(rp->rio_cnt < 0)
{
if(errno != EINTR)
return -1;
}
else if (rp->rio_cnt == 0)
return 0;
else
rp->rio_bufptr = rp->rio_buf;
}
size_t cnt = n > rp->rio_cnt ? rp->rio_cnt : n;
memcpy(userbuf, rp->rio_bufptr, cnt);
rp->rio_bufptr += cnt;
rp->rio_cnt -= cnt;
return cnt;
}
//讀文本行,複製到內存位置userbuf,最後在結尾添NULL(0)
ssize_t rio_readlineb(rio_t *rp, void *userbuf, size_t maxlen)
{
ssize_t readn;
char c;
char *buf = (char*)userbuf;
int i;
for(i = 1; i < maxlen; ++i)
{
readn = rio_read(rp, &c, 1);
if(readn < 0)
return -1;
else if (readn == 0)
{
if(i == 1)
return 0;
else
break;
}
else
{
*buf++ = c;
if(c == "
")
{
++i;
break;
}
}
}
*buf = 0;
return i-1;
}
//打開伺服器端監聽描述符
int open_listenfd(const char *port)
{
addrinfo hints, *listp, *p;
int listenfd, optval = 1;
memset(&hints, 0, sizeof(addrinfo));
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
hints.ai_flags |= AI_NUMERICSERV;
getaddrinfo(NULL, port, &hints, &listp);
for(p = listp; p; p = p->ai_next)
{
if((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
continue;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(int));
if(bind(listenfd, p->ai_addr, p->ai_addrlen) == 0)
break;
close(listenfd);
}
freeaddrinfo(listp);
if(!p)
return -1;
if(listen(listenfd, 3) < 0)
{
close(listenfd);
return -1;
}
return listenfd;
}
void doit(int fd); //http事務
void read_requesthdrs(rio_t *rp); //讀請求報頭並忽略
int parse_uri(char *uri, char *filename, char *cgiargs); //解析uri
void serve_static(int fd, char *filename, int filesize); //訪問靜態資源
void get_filetype(char *filename, char *filetype); //幾種支持的文件類型
void serve_dynamic(int fd, char *filename, char *cgiargs); //訪問動態資源
void clienterror(int fd, char *cause, int errnum, std::string shortmsg, std::string longmsg); //錯誤檢查
int MAXLINE = 100;
int main(int argc, const char * argv[]) {
// insert code here...
int listenfd, connfd;
char hostname[MAXLINE], port[MAXLINE];
socklen_t clientlen;
sockaddr_storage clientaddr;
if(argc!=2) //兩個命令行參數,可執行文件名和埠號
{
fprintf(stderr, "usage: %s <port>
", argv[0]);
exit(1);
}
listenfd = open_listenfd(argv[1]); //打開監聽描述符,argv[1]為埠號,這裡選擇8000
while(1)
{
clientlen = sizeof(clientaddr);
connfd = accept(listenfd, (sockaddr*)&clientaddr, &clientlen); //等待連接請求,創建已連接描述符
getnameinfo((sockaddr*)&clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE, 0); //獲得請求客戶端的信息
//客戶端主機名,埠號(埠號為臨時分配的一個值)
printf("Accepted connection from (%s, %s)
", hostname, port);
doit(connfd);
close(connfd);
printf("按任意鍵繼續");
getchar();
}
}
void doit(int fd)
{
int is_static;
struct stat sbuf;
char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
char filename[MAXLINE], cgiargs[MAXLINE];
rio_t rio;
rio_readinitb(&rio, fd);
rio_readlineb(&rio, buf, MAXLINE);//讀請求行
printf("Request headers:
");
printf("%s
", buf);
sscanf(buf, "%s %s %s",method, uri, version);
//只支持GET方法
if(strcasecmp(method, "GET"))
{
clienterror(fd, method, 501, "Not implemented", "Tiny does not implement this method");
return;
}
read_requesthdrs(&rio);//讀請求報頭
is_static = parse_uri(uri, filename, cgiargs);//解析URI,判斷靜態訪問還是動態
//通過文件名獲得文件信息,存於sbuf
if(stat(filename, &sbuf) < 0)
{
clienterror(fd, filename, 404, "Not Found", "Tiny couldn"t find the file");
return;
}
//靜態訪問
if(is_static)
{
if(!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode))
{
clienterror(fd, filename, 403, "Forbidden", "Tiny couldn"t read the file");
return;
}
serve_static(fd, filename, sbuf.st_size);
}
else
{
if(!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode))
{
clienterror(fd, filename, 403, "Forbidden", "Tiny couldn"t run the CGI program");
return;
}
//動態訪問
serve_dynamic(fd, filename, cgiargs);
}
}
void clienterror(int fd, char *cause, int errnum, std::string shortmsg, std::string longmsg)
{
char buf[MAXLINE], body[MAXLINE];
sprintf(body, "<html><title>Tiny ERROR</title>");
sprintf(body, "%s<body bgcolor=""ffffff"">
", body);
sprintf(body, "%s%d: %s
", body, errnum, shortmsg.c_str());
sprintf(body, "%s<p>%s: %s
", body, longmsg.c_str(), cause);
sprintf(body, "%s<hr><em>The Tiny Web server</em>
", body);
sprintf(buf, "HTTP/1.0 %d %s
",errnum, shortmsg.c_str());
rio_writen(fd, buf, strlen(buf));
sprintf(buf, "Content-type: text/html
");
rio_writen(fd, buf, strlen(buf));
sprintf(buf, "Content-length: %d
", (int)strlen(body));
rio_writen(fd, buf, strlen(buf));
rio_writen(fd, body, strlen(body));
}
void read_requesthdrs(rio_t *rp)
{
char buf[MAXLINE];
rio_readlineb(rp, buf, MAXLINE);
//找到最後一行,為空行(
)
while(strcmp(buf, "
"))
{
rio_readlineb(rp, buf, MAXLINE);
}
return;
}
int parse_uri(char *uri, char *filename, char *cgiargs)
{
char *ptr;
//認為動態資源存放於cgi-bin目錄
if(!strstr(uri, "cgi-bin"))
{
strcpy(cgiargs, "");
strcpy(filename, "/Users/wanghao/Desktop");//設置桌面為默認路徑
strcat(filename, uri);
if(uri[strlen(uri)-1] == "/")//路徑最後為/時添加的默認路徑
strcat(filename, "home.html");
return 1;
}
else
{
ptr = index(uri, "?");
if(ptr)
{
strcpy(cgiargs, ptr+1);
*ptr = " ";
}
else
{
strcpy(cgiargs, "");
}
strcpy(filename, "/Users/wanghao/Desktop");
strcat(filename, uri);
return 0;
}
}
void get_filetype(char *filename, char *filetype)
{
if(strstr(filename, ".html"))
strcpy(filetype, "text/html");
else if(strstr(filename, ".gif"))
strcpy(filetype, "image/gif");
else if(strstr(filename, ".png"))
strcpy(filetype, "image/png");
else if(strstr(filename, ".jpg"))
strcpy(filetype, "image/jpg");
else
strcpy(filetype, "text/plain");
}
void serve_static(int fd, char *filename, int filesize)
{
char *srcp, filetype[MAXLINE], buf[MAXLINE];
get_filetype(filename, filetype);
sprintf(buf, "HTTP/1.0 200 OK
");
sprintf(buf, "%sServer: Tiny Web Server
", buf);
sprintf(buf, "%sConnection: close
", buf);
sprintf(buf, "%sContent-length: %d
", buf, filesize);
sprintf(buf, "%sContent-type: %s
", buf, filetype);
rio_writen(fd, buf, strlen(buf));
printf("Response headers:
");
printf("%s", buf);
//只讀方式打開文件,將文件映射到虛擬內存
int srcfd = open(filename, O_RDONLY, 0);
srcp = (char*)mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);
close(srcfd);
rio_writen(fd, srcp, filesize);//輸出到客戶端
munmap(srcp, filesize);//內存回收
}
void serve_dynamic(int fd, char *filename, char *cgiargs)
{
char buf[MAXLINE], *emptylist[] = {NULL};
char *envp[]={0,NULL};
sprintf(buf, "HTTP/1.0 200 OK
");
rio_writen(fd, buf, strlen(buf));
sprintf(buf, "Server: Tiny Web Server
");
rio_writen(fd, buf, strlen(buf));
//開子進程,在子進程執行動態訪問
if(fork() == 0)
{
setenv("QUERY_STRING", cgiargs, 1);//獲得cgi參數
dup2(fd, STDOUT_FILENO);//重定位
execve(filename, emptylist, envp);
}
wait(NULL);
}
※Nginx 日誌和變數
※jsp頁面內嵌另一個jsp公共頁面
TAG:程序員小新人學習 |