Register | Login
Forum Index > User Tutorials > Tutorial :: Writing a small webserver
Author Message
Pages: 1
Admin
Site Admin

avatar

(send private message)

Posts: 923
Topics: 54

Location:
OverHertz Studio
[65] Tutorial :: Writing a small webserver - posted: 2011-09-16 22:21:01
In this tutorial I will write a simple web server, and i mean a very simple web server, i will take you step by step as i write it, allowing you to learn some of the language and at the same time end up with your own small simple web server to start out with. It is expected you have experience with winsock and the windows API.

ok first we need a basic ziron project file with a few extra includes, such as the winsock file and console.

Code:
program WIN32CUI 'Sample';

//cpu optimization option.
#cpu 486;

#include 'ziron32.zir';
#include 'fileio.zir';
#include 'wsock32.zir';
#include 'console.zir';

ExitProcess(0); 


ok our first task is to start listening on port 80, we will create a const for the port

Code:
const DEFAULT_PORT = 80;


let us add some variables for winsock also

Code:
WSADATA ws;
WSOCKET listensocket;
WSOCKET clientsocket;
sockaddr_in addrinfo;


we will now initialize winsock and begin listening

Code:
EAX = WSAStartup(WSOCKVER2_2, @ws);
listensocket = socket(PF_INET, SOCK_STREAM, 0);
addrinfo.sin_family = AF_INET;
addrinfo.sin_addr = INADDR_ANY;
addrinfo.sin_port = htons(DEFAULT_PORT);
bind(listensocket, @addrinfo, sizeof sockaddr_in);

listen(listensocket, SOMAXCONN);


next we need to accept connections, which we will do in a loop

Code:
println('started serving...');
while () {
  clientsocket = accept(listensocket, nil, nil);  
  
  //process something here
  
  //close the client gracefully
  shutdown(clientsocket, SD_BOTH);  
  closesocket(clientsocket);
}


ok now i'll add some variables for the web server side of things

Code:
//this const will be sent before sending content type
const HTTP_HEADER = 'HTTP/1.1 200 OK\nContent-Type: ';
//this variable will hold the number of bytes received from recv
DWord bytesReceived;
//this will be our buffer for incoming data
char inBuffer[65537];
//a handle for the file being output to the client
DWord hFile;
//this buffer will be for the file that is read from disk
char IndexPage[65535];
//this buffer is for our file name and path.
char fileNameBuf[256];


ok in our loop where the process comment is we will place some more code

Code:
  //receive some data
  bytesReceived = recv(clientsocket, @inBuffer, 65536, 0);
  ebx = @inBuffer;
  //add a null char at the end for safety
  char[ebx+eax] = 0;

  //if no bytes were incoming, do nothing
  if (bytesReceived != 0) {
    //if the incoming data is a GET request we will process it        
    if (char*[ebx] == ord('GET ')) {
      //call a process function we will write in a moment.
      processInBuffer();

      //try to open the requested file that our process function will get for us.
      hFile = fileopen(@fileNameBuf);
      //if the file exists and is ok we will output it
      if (hFile != INVALID_HANDLE_VALUE) {
        //we will reuse the bytesReceived instead of creating a new variable.
        ReadFile(hFile, @IndexPage, 65535, bytesReceived, nil);
        send(clientsocket, @IndexPage, bytesReceived, 0);    
        fileclose(hFile);      
      }
    }
    
    char[ebx] = 0;
  }
  


now we will write our process function

Code:
function processInBuffer() {
  uses ecx ebx;
  add ebx, 4 //skip past 'GET '
  ecx = xor;
  eax = @fileNameBuf;
  byte[eax] = ord('.');
  inc eax;

  while (byte[ebx+ecx] <> ord(' ')) {
    dl = [ebx+ecx];
    [eax+ecx] = dl;
    
    inc ecx;
  }
  [eax+ecx] = 0;
  
  
  if (word[eax] == "/") {
    strlcpy(@fileNameBuf, 'index.html', 10);
    byte[eax+11] = 0;
    
    send(clientsocket, HTTP_HEADER, 30, 0);    
    send(clientsocket, 'text/html\n\n', 11, 0);
  } else {
    eax = getFileExt(eax);
    
    if (char*[eax] == "html") {
      send(clientsocket, HTTP_HEADER, 30, 0);    
      send(clientsocket, 'text/html\n\n', 11, 0);
      
    } elseif (char*[eax] == "css") {
      send(clientsocket, HTTP_HEADER, 30, 0);
      send(clientsocket, 'text/css\n\n', 10, 0);
      
    } elseif (char*[eax] == "gif") {
      send(clientsocket, HTTP_HEADER, 30, 0);
      send(clientsocket, 'image/gif\n\n', 11, 0);
      
    } elseif (char*[eax] == "png") {
      send(clientsocket, HTTP_HEADER, 30, 0);
      send(clientsocket, 'image/png\n\n', 11, 0);
      
    } elseif ((char*[eax] == "jpg") or (char*[eax] == "jpeg")) {
      send(clientsocket, HTTP_HEADER, 30, 0);
      send(clientsocket, 'image/jpeg\n\n', 12, 0);    
    }
  }
}


i will leave this function for you to study, if you have questions, please do reply.
well, that is it, we have build a tiny web server, bare in mind it has no security and many other features are missing that you can add, maybe in later tutorials i can extend this web server.

the full code for this tutorial:

Code:
program WIN32CUI 'Sample';

//cpu optimization option.
#cpu 486;

#include 'ziron32.zir';
#include 'fileio.zir';
#include 'wsock32.zir';
#include 'console.zir';

/////////////////////////////////////////////////

const DEFAULT_PORT = 80;

WSADATA ws;
WSOCKET listensocket;
WSOCKET clientsocket;
sockaddr_in addrinfo;

EAX = WSAStartup(WSOCKVER2_2, @ws);
listensocket = socket(PF_INET, SOCK_STREAM, 0);
addrinfo.sin_family = AF_INET;
addrinfo.sin_addr = INADDR_ANY;
addrinfo.sin_port = htons(DEFAULT_PORT);
bind(listensocket, @addrinfo, sizeof sockaddr_in);
    
const HTTP_HEADER = 'HTTP/1.1 200 OK\nContent-Type: ';
DWord bytesReceived;
char inBuffer[65537];
DWord hFile;
char IndexPage[65535];
char fileNameBuf[256];

listen(listensocket, SOMAXCONN);


/////////////////////////////////////////////////


function processInBuffer() {
  uses ecx ebx;
  add ebx, 4 //skip past 'GET '
  ecx = xor;
  eax = @fileNameBuf;
  byte[eax] = ord('.');
  inc eax;

  while (byte[ebx+ecx] <> ord(' ')) {
    dl = [ebx+ecx];
    [eax+ecx] = dl;
    
    inc ecx;
  }
  [eax+ecx] = 0;
  
  
  if (word[eax] == "/") {
    strlcpy(@fileNameBuf, 'index.html', 10);
    byte[eax+11] = 0;
    
    send(clientsocket, HTTP_HEADER, 30, 0);    
    send(clientsocket, 'text/html\n\n', 11, 0);
  } else {
    eax = getFileExt(eax);
    
    if (char*[eax] == "html") {
      send(clientsocket, HTTP_HEADER, 30, 0);    
      send(clientsocket, 'text/html\n\n', 11, 0);
      
    } elseif (char*[eax] == "css") {
      send(clientsocket, HTTP_HEADER, 30, 0);
      send(clientsocket, 'text/css\n\n', 10, 0);
      
    } elseif (char*[eax] == "gif") {
      send(clientsocket, HTTP_HEADER, 30, 0);
      send(clientsocket, 'image/gif\n\n', 11, 0);
      
    } elseif (char*[eax] == "png") {
      send(clientsocket, HTTP_HEADER, 30, 0);
      send(clientsocket, 'image/png\n\n', 11, 0);
      
    } elseif ((char*[eax] == "jpg") or (char*[eax] == "jpeg")) {
      send(clientsocket, HTTP_HEADER, 30, 0);
      send(clientsocket, 'image/jpeg\n\n', 12, 0);    
    }
  }
}

println('started serving...');

while () {
  clientsocket = accept(listensocket, nil, nil);  
  
  bytesReceived = recv(clientsocket, @inBuffer, 65536, 0);
  ebx = @inBuffer;
  char[ebx+eax] = 0;

  if (bytesReceived != 0) {        
    if (char*[ebx] == ord('GET ')) {
      processInBuffer();

      hFile = fileopen(@fileNameBuf);
      if (hFile != INVALID_HANDLE_VALUE) {
        //we will reuse the bytesReceived instead of creating a new variable.
        ReadFile(hFile, @IndexPage, 65535, bytesReceived, nil);
        send(clientsocket, @IndexPage, bytesReceived, 0);    
        fileclose(hFile);      
      }
    }
    
    char[ebx] = 0;
  }
  
  shutdown(clientsocket, SD_BOTH);  
  closesocket(clientsocket);
}

ExitProcess(0); 


note you need to place an index.html inside the same dir as the executable, or you can put the address to a specified file.

let me know what you think. smile

Download Ziron
Get free hosting for Ziron related fan-sites and Ziron projects, contact me in private message.
Admin
Site Admin

avatar

(send private message)

Posts: 923
Topics: 54

Location:
OverHertz Studio
[66] - posted: 2011-09-17 12:01:37
with each new post, hopefully i can add something new...

ok, lets add the possibility to download larger files, no cutoffs

let us change the following code in the while loop
Code:
  ReadFile(hFile, @IndexPage, 65535, bytesReceived, nil);
  send(clientsocket, @IndexPage, bytesReceived, 0);    
  fileclose(hFile); 


to

Code:
  repeat {
    ReadFile(hFile, @IndexPage, 65535, @bytesReceived, nil);
    if (bytesReceived == 0) break;
    send(clientsocket, @IndexPage, bytesReceived, 0);    
  };
  fileclose(hFile);


so now if we download a file larger then 64kb, it will not be a problem smile

Download Ziron
Get free hosting for Ziron related fan-sites and Ziron projects, contact me in private message.
Admin
Site Admin

avatar

(send private message)

Posts: 923
Topics: 54

Location:
OverHertz Studio
[68] - posted: 2011-09-17 18:27:36
made a few extra updates to the code, if you have questions about it, just ask, it still has some major things needed, for example multitasking...right now only 1 user actually downloads a file at a time.

Code:
program WIN32CUI 'Sample';

//cpu optimization option.
#cpu 486;

#include 'ziron32.zir';
#include 'fileio.zir';
#include 'wsock32.zir';
#include 'console.zir';

/////////////////////////////////////////////////

const DEFAULT_PORT = 80;

WSADATA ws;
WSOCKET listensocket;
WSOCKET clientsocket;
sockaddr_in addrinfo;

EAX = WSAStartup(WSOCKVER2_2, @ws);
listensocket = socket(PF_INET, SOCK_STREAM, 0);
addrinfo.sin_family = AF_INET;
addrinfo.sin_addr = INADDR_ANY;
addrinfo.sin_port = htons(DEFAULT_PORT);
bind(listensocket, @addrinfo, sizeof sockaddr_in);
    
const HTTP_HEADER = 'HTTP/1.1 200 OK\nContent-Type: ';
const HTTP_NOTFOUND = 'HTTP/1.1 404 Not Found';

DWord bytesReceived;
char inBuffer[65537];
DWord hFile;
char IndexPage[65535];
char fileNameBuf[256];

//WSOCKET clientsocks[65535];

listen(listensocket, SOMAXCONN);


/////////////////////////////////////////////////


function processInBuffer() {
  uses ecx ebx;
  add ebx, 4 //skip past 'GET '
  ecx = xor;
  eax = @fileNameBuf;
  byte[eax] = ".";
  inc eax;

  while (byte[ebx+ecx] <> " ") {
    dl = [ebx+ecx];
    [eax+ecx] = dl;
    
    inc ecx;
  }
  [eax+ecx] = 0;
}

println('started serving...');

while () {
  clientsocket = accept(listensocket, nil, nil);  
  
  bytesReceived = recv(clientsocket, @inBuffer, 65536, 0);
  ebx = @inBuffer;
  char[ebx+eax] = 0;

  if (bytesReceived != 0) {        
    if (char*[ebx] == "GET ") {
      processInBuffer();
      
      eax = getFileExt(eax);    
      if (char[eax] == "") {
        eax--;
        if (char[eax] == "/") {
          strlcpy(eax, '/index.html', 11);
          byte[eax+11] = 0;
          add eax, 7
        }
      }
      push eax
      
      hFile = fileopen(@fileNameBuf);
      if (hFile != INVALID_HANDLE_VALUE) {
        pop eax
            
        if (char*[eax] == "html") {
          send(clientsocket, HTTP_HEADER, 30, 0);    
          send(clientsocket, 'text/html\n\n', 11, 0);
            
        } elseif (char*[eax] == "css") {
          send(clientsocket, HTTP_HEADER, 30, 0);
          send(clientsocket, 'text/css\n\n', 10, 0);
            
        } elseif (char*[eax] == "gif") {
          send(clientsocket, HTTP_HEADER, 30, 0);
          send(clientsocket, 'image/gif\n\n', 11, 0);
            
        } elseif (char*[eax] == "png") {
          send(clientsocket, HTTP_HEADER, 30, 0);
          send(clientsocket, 'image/png\n\n', 11, 0);
            
        } elseif ((char*[eax] == "jpg") or (char*[eax] == "jpeg")) {
          send(clientsocket, HTTP_HEADER, 30, 0);
          send(clientsocket, 'image/jpeg\n\n', 12, 0);    
        }      
      
        //we will reuse the bytesReceived instead of creating a new variable.
        repeat {
          ReadFile(hFile, @IndexPage, 65535, @bytesReceived, nil);
          if (bytesReceived == 0)  break;
          send(clientsocket, @IndexPage, bytesReceived, 0);    
        };
        fileclose(hFile);
      } else {
        pop eax
        send(clientsocket, HTTP_NOTFOUND, 22, 0);  
      }
    }
    
    char[ebx] = 0;
  }
  
  shutdown(clientsocket, SD_BOTH);  
  closesocket(clientsocket);
}



ExitProcess(0); 


Download Ziron
Get free hosting for Ziron related fan-sites and Ziron projects, contact me in private message.
Pages: 1
create new reply


Quick reply:

Message:



Currently Active Users:
There are currently 1 user(s) online. 0 member(s) and 1 guest(s)
Most users ever online was 533, May 5, 2016, 7:19 am.


Statistics:
Threads: 223 | Posts: 1835 | Members: 50 | Active Members: 50
Welcome to our newest member, NobodyAtWork
const Copyright = '2011-2017 © OverHertz Ltd. All rights reserved.';
Web development by OverHertz Ltd