mirror of https://github.com/macssh/macssh.git
257 lines
9.7 KiB
C++
Executable File
257 lines
9.7 KiB
C++
Executable File
// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
// % Project : GUSI - Grand Unified Socket Interface
|
|
// % File : TestHttpD.nw - A primitive HTTP daemon
|
|
// % Author : Matthias Neeracher
|
|
// % Language : C++
|
|
// %
|
|
// % $Log$
|
|
// % Revision 1.1.1.1 2001/03/03 21:51:12 chombier
|
|
// % Initial import
|
|
// %
|
|
// % Revision 1.5 1999/06/08 04:31:32 neeri
|
|
// % Getting ready for 2.0b2
|
|
// %
|
|
// % Revision 1.4 1999/03/17 09:05:16 neeri
|
|
// % Added GUSITimer, expanded docs
|
|
// %
|
|
// % Revision 1.3 1998/10/25 11:57:42 neeri
|
|
// % Ready to release 2.0a3
|
|
// %
|
|
// % Revision 1.2 1998/10/11 16:45:28 neeri
|
|
// % Ready to release 2.0a2
|
|
// %
|
|
// % Revision 1.1 1998/08/01 21:32:14 neeri
|
|
// % About ready for 2.0a1
|
|
// %
|
|
// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
//
|
|
// \documentstyle[noweb]{article}
|
|
// \begin{document}
|
|
// \section{A primitive HTTP daemon}
|
|
//
|
|
// This program implements an extremely primitive HTTP daemon.
|
|
//
|
|
//
|
|
// <TestHttpD.cp>=
|
|
#include <unistd.h>
|
|
#include <time.h>
|
|
#include <pthread.h>
|
|
#include <iostream.h>
|
|
#include <netinet/in.h>
|
|
#include <sys/socket.h>
|
|
#include <arpa/inet.h>
|
|
#include <netdb.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
|
|
#include <GUSIDiag.h>
|
|
|
|
// The daemon runs until something is written to [[gDone]] and [[gPending]] is 0.
|
|
//
|
|
// <Global state for [[TestHttpD]]>=
|
|
int gDone = 0;
|
|
int gPending = 0;
|
|
pthread_mutex_t gPendingMutex = PTHREAD_MUTEX_INITIALIZER;
|
|
pthread_cond_t gPendingCond = PTHREAD_COND_INITIALIZER;
|
|
// All our replies use the same header
|
|
//
|
|
// <Global state for [[TestHttpD]]>=
|
|
const char * gReplyHeader =
|
|
"HTTP/1.0 200 OK\015\012"
|
|
"Content-Type: text/plain\015\012\015\012";
|
|
const char * gErrorHeader =
|
|
"HTTP/1.0 404 Not Found\015\012"
|
|
"Content-Type: text/plain\015\012\015\012";
|
|
// <Auxiliary procedures for [[TestHttpD]]>=
|
|
void HandleCGI(int socket, char * request)
|
|
{
|
|
if (!strcmp(request, "quit")) {
|
|
write(socket, gReplyHeader, strlen(gReplyHeader));
|
|
write(socket, "So long.", 8);
|
|
pthread_mutex_lock(&gPendingMutex);
|
|
write(gDone, "Game Over", 9);
|
|
pthread_mutex_unlock(&gPendingMutex);
|
|
} else if (!strcmp(request, "slow")) {
|
|
write(socket, gReplyHeader, strlen(gReplyHeader));
|
|
for (int i = 0; i<120; ++i) {
|
|
char buf[20];
|
|
sprintf(buf, "%d\015\012", i);
|
|
write(socket, buf, strlen(buf));
|
|
sleep(1);
|
|
}
|
|
}
|
|
}
|
|
// <Auxiliary procedures for [[TestHttpD]]>=
|
|
void HandleFile(int socket, char * request)
|
|
{
|
|
for (char * slash = request; slash = strchr(slash, '/'); )
|
|
*slash = ':';
|
|
FILE * file = fopen(request, "r");
|
|
FILE * sock = fdopen(socket, "w");
|
|
|
|
if (file) {
|
|
fputs(gReplyHeader, sock);
|
|
// <Copy [[file]] to [[sock]] with EOL translation>=
|
|
int ch;
|
|
|
|
while ((ch = fgetc(file)) != EOF) {
|
|
fputc(ch, sock);
|
|
if (ch == '\015') {
|
|
fputc('\012', sock);
|
|
if (ferror(sock))
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
fputs(gErrorHeader, sock);
|
|
// <Report an error>=
|
|
fputs("Yo! That URL is busted!\015\012", sock);
|
|
}
|
|
fclose(file);
|
|
fclose(sock);
|
|
}
|
|
// <Request handler for [[TestHttpD]]>=
|
|
extern "C" void * Handler(void * s)
|
|
{
|
|
char buffer[1000];
|
|
int size = 999;
|
|
char * request;
|
|
|
|
int socket = reinterpret_cast<int>(s);
|
|
int sz;
|
|
bool foundEnd = false;
|
|
|
|
for (request = buffer; !foundEnd && (sz = read(socket, request, size)) > 0; ) {
|
|
// <Translate CRLF and set [[foundEnd]] for end of request>=
|
|
char * in = request;
|
|
char * out= request;
|
|
bool nl = false;
|
|
while (sz--) {
|
|
switch (*in) {
|
|
case 0x0A:
|
|
if (out != request && out[-1] == 0x0D)
|
|
break; // Skip
|
|
*in = 0x0D;
|
|
// Fall through
|
|
case 0x0D:
|
|
if (nl) {
|
|
foundEnd = true;
|
|
sz = 0;
|
|
}
|
|
// Fall through
|
|
default:
|
|
nl = (*out++ = *in) == 0x0D;
|
|
}
|
|
++in;
|
|
}
|
|
sz = out-request;
|
|
request += sz;
|
|
size -= sz;
|
|
}
|
|
|
|
// The technique employed here should definitely not be confused with a real
|
|
// WWW server. All we're interested here is demonstrating the I/O library.
|
|
//
|
|
// <Isolate URL>=
|
|
for (request = strchr(buffer, ' '); isspace(*++request); )
|
|
;
|
|
*strchr(request, ' ') = 0;
|
|
// We handle a few special URLs, the rest is translated as files.
|
|
//
|
|
// <Handle URL>=
|
|
if (!strncmp(request, "/cgi/", 5))
|
|
HandleCGI(socket, request+5);
|
|
else
|
|
HandleFile(socket, request);
|
|
close(socket);
|
|
|
|
pthread_mutex_lock(&gPendingMutex);
|
|
--gPending;
|
|
pthread_cond_signal(&gPendingCond);
|
|
pthread_mutex_unlock(&gPendingMutex);
|
|
|
|
return 0;
|
|
}
|
|
// <Main program for [[TestHttpD]]>=
|
|
extern char * GUSI_diag_log;
|
|
|
|
void main()
|
|
{
|
|
GUSI_diag_log = "TestHTTPd";
|
|
// First we create a listener on port 80.
|
|
//
|
|
// <Create listener socket>=
|
|
sockaddr_in addr;
|
|
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_addr.s_addr= 0;
|
|
addr.sin_port = 80;
|
|
|
|
int listener = socket(AF_INET, SOCK_STREAM, 0);
|
|
|
|
bind(listener, (sockaddr *)&addr, sizeof(addr));
|
|
listen(listener, 5);
|
|
// To make it possible to wait on either a socket connection or a quit event, we
|
|
// transmit the latter on a pipe. Cool, isn't it?
|
|
//
|
|
// <Create completion pipe>=
|
|
int done[2];
|
|
pipe(done);
|
|
gDone = done[1];
|
|
// The main event loop is a rather vanilla [[select]] based loop.
|
|
//
|
|
// <Handle events>=
|
|
fd_set fd_interesting;
|
|
FD_ZERO(&fd_interesting);
|
|
FD_SET(done[0], &fd_interesting);
|
|
FD_SET(listener, &fd_interesting);
|
|
|
|
for (;;) {
|
|
fd_set fd = fd_interesting;
|
|
|
|
if (select(32, &fd, NULL, NULL, NULL)) {
|
|
if (FD_ISSET(listener, &fd)) {
|
|
// Each incoming connection is immediately delegated to a new thread.
|
|
//
|
|
// <Create a new client>=
|
|
int client = accept(listener, NULL, NULL);
|
|
pthread_t thread;
|
|
|
|
pthread_create(&thread, 0, Handler, (void *)client);
|
|
pthread_detach(thread);
|
|
|
|
pthread_mutex_lock(&gPendingMutex);
|
|
++gPending;
|
|
pthread_mutex_unlock(&gPendingMutex);
|
|
// Looking up the IP number is quite slow, so we only do that after starting the
|
|
// thread.
|
|
//
|
|
// <Report connection>=
|
|
sockaddr_in peer;
|
|
socklen_t len = sizeof(sockaddr_in);
|
|
|
|
if (getpeername(client, (sockaddr *)&peer, &len)) {
|
|
cerr << "getpeername failed: " << h_errno << endl;
|
|
} else {
|
|
hostent * host = gethostbyaddr(&peer.sin_addr, sizeof(in_addr), AF_INET);
|
|
if (host)
|
|
GUSI_MESSAGE(("Connection from %s [%s]\n", host->h_name, inet_ntoa(peer.sin_addr)));
|
|
else
|
|
GUSI_MESSAGE(("Connection from %s\n", inet_ntoa(peer.sin_addr)));
|
|
}
|
|
}
|
|
if (FD_ISSET(done[0], &fd))
|
|
break;
|
|
}
|
|
}
|
|
// Finally, we wait for [[gPending]] to reach zero again, at which point all
|
|
// clients will have completed.
|
|
//
|
|
// <Wait for all clients to complete>=
|
|
pthread_mutex_lock(&gPendingMutex);
|
|
while (gPending)
|
|
pthread_cond_wait(&gPendingCond, &gPendingMutex);
|
|
pthread_mutex_unlock(&gPendingMutex);
|
|
}
|