티스토리 뷰

흔석/숙제

웹서버 소스

JOHNPARK82 2006. 4. 13. 03:53

/************************************************************************
Multithreaded Web Server

written by eternalbleu
************************************************************************/
#include <STDIO.H>
#include <STDLIB.H>
#include <STRING.H>
#include <WINSOCK2.H>
#include <PROCESS.H>

// Constant
#define HUG_BUFFSIZE 2048
#define MID_BUFFSIZE 1024
#define SMA_BUFFSIZE 32
#define MAX_FILENAME 256

// Error Code
#define STATECODE_BAD_REQUEST 400
#define STATECODE_NOT_FOUND 404
#define STATECODE_OK 200

// Send HTTP Replay to Client
void SendReply(SOCKET clnt, UINT statusType, char *filename);
char* GetStatusLine(UINT type = STATECODE_OK);
char* GetErrorContent(UINT type);
int GetContentLength(FILE* fp);
char* GetContentType(char* filename);

void ErrorHandling(const char* message); // Error Message Handling
UINT WINAPI ClientConnect(void* argv); // Thread Proc

int main(int argc, char** argv)
{
WSADATA wsaData;
SOCKET servSock;
SOCKET clntSock;
SOCKADDR_IN servAddr;
SOCKADDR_IN clntAddr;

HANDLE hThread;
DWORD dwThreadID;

if (argc != 2) {
printf("SIMPLE WEBSERVER. written by youngchang. \n USAGE: %s <PORT>\n", argv[0]);
exit(1);
}

// DLL loading
if ( WSAStartup(MAKEWORD(2, 2), &amp;wsaData) != 0 )
ErrorHandling("WSAStartup() error occured.");

// socket creation &amp; initialize
if ( (servSock = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET )
ErrorHandling("socket() error occured.");

// 서버 소켓 설정
memset((void*)&amp;servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET; // IPv4 주소 체계 이용
servAddr.sin_addr.s_addr = htonl( INADDR_ANY ); // 서버 주소 할당. 임의의 주소가 될 수 있어야함.
servAddr.sin_port = htons( atoi(argv[1]) ); // 인자로 받은 숫자를 이용 포트를 할당

// server socket setting
if ( bind(servSock, (SOCKADDR*)&amp;servAddr, sizeof(servAddr) ) == SOCKET_ERROR )
ErrorHandling("bind() error occured.");

// wait for request from client
if ( listen(servSock, 5) == SOCKET_ERROR )
ErrorHandling("listen() error occured.");

// requested from client
while(1) {
// 서버 대기 소켓으로 연결 요청이 들어온 클라이언트와의 소켓을 생성
int clntAddrSize = sizeof(clntAddr);
if ( (clntSock = accept(servSock, (SOCKADDR*)&amp;clntAddr, &amp;clntAddrSize)) == INVALID_SOCKET )
ErrorHandling("accept() error occured.");

// 클라이언트의 정보 출력
#ifdef _DEBUG
printf("REQUEST FROM %s : %d\n", inet_ntoa(clntAddr.sin_addr), ntohs(clntAddr.sin_port));
#endif
// 클라이언트 연결 쓰레드 생성
hThread = (HANDLE)_beginthreadex(NULL, 0, ClientConnect, (void*)clntSock, 0, (unsigned*)&amp;dwThreadID);
if (hThread == NULL)
ErrorHandling("_beginthreadex() error occured.");
}

// socket destroy
closesocket(servSock);

// DLL Unloading
WSACleanup();
return 0;
}

/************************************************************************
ClientConnect 합당한 에러메시지를 출력하며 프로그램을 종료한다.

@ argv 쓰레드프로세저의 인자. 이 경우 클라이언트와 연결된 소켓이 인자임.

# return 에러발생시 1, 정상 종료시 0
************************************************************************/
UINT WINAPI ClientConnect(void* argv)
{
SOCKET clntSock = (SOCKET)argv; // 클라이언트 소켓
char request[HUG_BUFFSIZE] = {0,}; // 요청 라인 (string)
char filename[MAX_FILENAME] = {0,}; // 요청 파일 (string)
char method[SMA_BUFFSIZE] = {0,}; // 요청 방식 (METHOD)

recv(clntSock, request, sizeof(request), 0);

if ( strstr(request, "HTTP/") == NULL ) // HTTP 요청인지 검사
{
closesocket(clntSock);
return 1;
}

// HTTP 요청 정보 출력
#ifdef _DEBUG
fputs("[BEG]\n", stdout);
fputs(request, stdout);
fputs("\n[END]\n", stdout);
#endif

strcpy(method, strtok(request, " ")); // HTTP 요청 방식 (METHOD) 얻기
if (strcmp(method, "GET")) // POST 메소드인 경우 에러 처리
SendReply(clntSock, STATECODE_BAD_REQUEST, NULL);

strcpy(filename, strtok(NULL, " ")); // HTTP 요청 파일 얻기

SendReply(clntSock, STATECODE_OK, filename);
return 0;
}

/************************************************************************
SendReply 클라이언트에게 알맞은 정보를 송신한다.

@ clnt 연결된 클라이언트와의 통신 소켓
@ statusType 답신 메시지의 종류를 송신한다
@ filename 클라이언트에게 보낼 파일 이름
************************************************************************/
void SendReply(SOCKET clnt, UINT statusType, char *filename)
{
// HEADER
char statusLine[SMA_BUFFSIZE] = {0,}; // 상태 라인
char contentLength[SMA_BUFFSIZE]= {0,}; // 내용 길이
char contentType[SMA_BUFFSIZE] = {0,}; // 내용 종류
char serverName[] = "Server:Simple Web Server v1.0\r\n"; // 서버 정보

// DATA
FILE* fp; // 전송 파일 파온터
char buffer[HUG_BUFFSIZE] = {0,}; // 전송 버퍼

// pre-process filename
if ( strlen(filename) == 1 &amp;&amp; !strcmp(filename, "/") ) strcpy(filename, "/index.html");
if ( *filename == '/' ) filename++;

// file open
if ( statusType == STATECODE_OK &amp;&amp; (fp = fopen(filename, "rb")) == NULL ) {
statusType = STATECODE_NOT_FOUND;
}
strcpy(statusLine, GetStatusLine(statusType)); // 상태 라인 정보 얻기
strcpy(contentType, GetContentType(filename)); // 내용 종류 얻기
sprintf(contentLength, "Content-length:%d\r\n", GetContentLength(fp)); // 내용 길이 정보 얻기

// Send HTTP Header
send(clnt, statusLine, strlen(statusLine), 0);
send(clnt, serverName, strlen(serverName), 0);
send(clnt, contentLength, strlen(contentLength), 0);
send(clnt, contentType, strlen(contentType), 0);

// Transmit error page data
if (statusType != STATECODE_OK)
{
char tmpbuf[MID_BUFFSIZE] = {0,};
strcpy(tmpbuf, GetErrorContent(statusType));
send(clnt, tmpbuf, strlen(tmpbuf), 0);
closesocket(clnt);
return;
}

// Send HTTP Data
size_t length = 0;
do {
length = fread(buffer, sizeof(char), sizeof(buffer), fp);
send (clnt, buffer, length, 0);
} while(!feof(fp));

closesocket(clnt);
}

/************************************************************************
GetStatusLine
@ type 상태 라인을 얻기위한 상수 값

# return 상태 라인 정보
************************************************************************/
char* GetStatusLine(UINT type)
{
switch(type)
{
case STATECODE_OK:
return "HTTP/1.0 200 OK\r\n";
break;

case STATECODE_BAD_REQUEST:
return "HTTP/1.0 400 Bad Request\r\n";
break;

case STATECODE_NOT_FOUND:
return "HTTP/1.0 404 Not Found\r\n";
break;

default:
return "HTTP/1.0 400 Bad Request\r\n";
break;
}
}

/************************************************************************
GetErrorContent 에러 코드에 따른 페이지 전송

@ type 에러 코드 상수

# return 에러코드에 맞는 페이지의 스트링
************************************************************************/
char* GetErrorContent(UINT type)
{
char buffer[HUG_BUFFSIZE] = {0,};
switch(type)
{
case STATECODE_NOT_FOUND:
strcpy(buffer,
"<FONT size=5>404 NOT FOUND</FONT>
ERROR OCCURED. CHECK FILENAME."
);
break;
case STATECODE_BAD_REQUEST:
strcpy(buffer,
"<FONT size=5>400 BAD REQUEST</FONT>
ERROR OCCURED. CHECK REQ METHOD."
);
break;
default:
strcpy(buffer,
"<FONT size=5>400 BAD REQUEST</FONT>
ERROR OCCURED. CHECK REQ METHOD."
);
break;
}
return buffer;
}

/************************************************************************
GetContentType 파일의 이름을 기반으로 파일의 MIME 타입의 스트링을 리턴한다.

@ filename 타입을 알아야할 파일의 이름

# return 파일의 MIME 타입 스트링
************************************************************************/
char* GetContentType(char* filename)
{
char retvalue[SMA_BUFFSIZE];

for(UINT i = 0; i &lt; strlen(filename); i++)
*(filename+i) = tolower( *(filename+i) );

if ( strstr(filename, ".html") != NULL || strstr(filename, ".htm") != NULL )
sprintf(retvalue, "Content-type:%s\r\n\r\n", "text/html");

if ( strstr(filename, ".txt") != NULL )
sprintf(retvalue, "Content-type:%s\r\n\r\n", "text/plain");

if ( strstr(filename, ".jpeg") != NULL || strstr(filename, ".jpg") != NULL )
sprintf(retvalue, "Content-type:%s\r\n\r\n", "image/jpeg");

if ( strstr(filename, ".png") != NULL )
sprintf(retvalue, "Content-type:%s\r\n\r\n", "image/pjpeg");

if ( strstr(filename, ".gif") != NULL )
sprintf(retvalue, "Content-type:%s\r\n\r\n", "image/gif");

if ( strstr(filename, ".bmp") != NULL )
sprintf(retvalue, "Content-type:%s\r\n\r\n", "image/x-xbitmap");

return retvalue;
}

/************************************************************************
GetContentLength 요청 받은 파일의 크기를 바이트 단위로 리턴한다. (in byte)

@ fp 요청 받은 파일의 포인터

# return file size (in byte)
************************************************************************/
int GetContentLength(FILE* fp)
{
if (fp == NULL) return 0;

unsigned int length = 0;
char buf[HUG_BUFFSIZE];
while ( !feof(fp) )
{
length += fread(buf, sizeof(char), sizeof(buf), fp);
}
fseek(fp, 0, SEEK_SET); //rewind(fp);

return length;
}

/************************************************************************
ErrorHandling 합당한 에러메시지를 출력하며 프로그램을 종료한다.

@ message 프로그램 에러 메시지
************************************************************************/
void ErrorHandling(const char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}


윈도우 환경에서 동작하는 서버입니다. 약간의 수정을 하면 리눅스 환경에서도 사용 가능함.