Added basic files

Added basic files:
 - httpd.asm - main loop server, include other files;
 - httpd_lib - file for data(constants, response string, headers etc.);
 - parser.inc - function for generation structure of HTTP request;
 - settings.inc - description request structure and function for read config file;
 - sys_func.inc - list function, for  worked with sockets, filesystem  and other functions system;
NOTE:
 The server does not work in this version, but the main loop and the parser work.
This commit is contained in:
Doczom
2023-11-12 16:02:12 +05:00
committed by GitHub
parent 7f900b3698
commit 3d3fa69481
6 changed files with 721 additions and 0 deletions

192
httpd.asm Normal file
View File

@@ -0,0 +1,192 @@
;*****************************************************************************;
; Copyright (C) 2023, Mikhail Frolov aka Doczom . All rights reserved. ;
; Distributed under terms of the 3-Clause BSD License. ;
; ;
; httpd - Simple http server for Kolibri OS. ;
; ;
; Version 0.0.1, 12 November 2023 ;
; ;
;*****************************************************************************;
use32
org 0
include 'sys_func.inc'
include 'settings.inc'
start:
mcall 68, 11 ; init heap
mcall 40, EVM_STACK ;set event bitmap
mov ecx, PATH
cmp byte[ecx],0
jnz @f
mov ecx, default_ini_path
@@:
; get settings
call load_settings ; ecx -> string to config file
test eax, eax
jnz .err_settings
;init server socket
push dword SO_NONBLOCK
push dword SOCK_STREAM
push dword AF_INET4
call netfunc_socket; AF_INET4, SOCK_STREAM, SO_NONBLOCK ; we dont want to block on accept
cmp eax, -1
je .sock_err
mov [srv_socket], eax
push srv_sockaddr.length
push dword srv_sockaddr
push dword[srv_socket]
call netfunc_bind; [srv_socket], srv_sockaddr, srv_sockaddr.length
cmp eax, -1
je .bind_err
; listen()
push dword[srv_backlog]
push dword[srv_socket]
call netfunc_listen; [srv_socket], [srv_backlog]
cmp eax, -1
jz .listen_err
.mainloop:
mcall 23, 100 ; get event to network stack
test eax, eax
jz .mainloop
push dword thread_connect
call CreateThread ; not save PID
jmp .mainloop
.listen_err:
.bind_err:
push dword[srv_socket]
call close; [srv_socket]
..err_settings:
.sock_err:
mcall -1
thread_connect:
sub esp, sizeof.CONNECT_DATA
mcall 40, EVM_STACK ; set event bitmap - network event
; ожидание подключения Accept, sockaddr находится на вершине стека нового потока
lea edx, [esp + CONNECT_DATA.sockaddr] ; new sockaddr
push dword 16 ; 16 byte - sockaddr length
push edx
push dword[srv_socket]
call netfunc_accept
cmp eax, -1
jz .err_accept
mov [esp + CONNECT_DATA.socket], eax
mov ebp, esp
; соединение установленно, теперь нужно выделить буфер(тоже на 16 кб наверное), и
; прочитать в этот буфер из сокета, когда прочтём ноль(или больше 4 кб), тогда
; выходим из цикла и анализируем заголовки и стартовую стоку.
mov esi, 0x8000
push esi
call Alloc ;alloc memory 32 kib
test eax, eax
jz .err_alloc
mov [esp + CONNECT_DATA.buffer_request], eax
mov edx, eax
mov esi, esp ; SAVE CONNECT_DATA
mov dword[esi + CONNECT_DATA.request_size], 0
@@:
;read data from socket
push dword 0 ;flags
mov eax, [esi + CONNECT_DATA.request_size]
push dword 0x8000
sub [esp], eax
push dword[esi + CONNECT_DATA.buffer_request]
add [esp], eax
push dword[esi + CONNECT_DATA.socket]
call netfunc_recv
cmp eax, -1
jz .err_recv_sock
test eax, eax
jz @f
add [esi + CONNECT_DATA.request_size], eax
cmp [esi + CONNECT_DATA.request_size], 0x8000 ; check end buffer
jb @b
@@:
; после получения всего запроса(более или менее всего) выделяем озу для
; ассоциативного массива заголовков и аргументов запроса
; 8*50 + 8*100
; esp .. esp + 1024 -> for http headers
; esp + 1024 .. esp + 2048 -> for URI args
sub esp, 2048
; parse http message
call parse_http_query ; ecx - buffer edx - length data in buffer
test eax, eax
jz .err_parse
; вызов нужной функции из списка моделей
; TODO
; end work thread
jmp .end_work
.err_parse:
; send error 501
.end_work:
add esp, 2048
; free OUT buffer
cmp dword[esp + CONNECT_DATA.buffer_response], 0
jz .err_recv_sock
push dword[esp + CONNECT_DATA.buffer_response]
call Free
.err_recv_sock:
; free IN buffer
cmp dword[esp + CONNECT_DATA.buffer_request], 0
jz .err_alloc
push dword[esp + CONNECT_DATA.buffer_request]
call Free
.err_alloc:
push dword[esp + CONNECT_DATA.socket]
call netfunc_close
.err_accept:
lea ecx,[esp + sizeof.CONNECT_DATA - 0x4000] ; get pointer to alloc memory
mcall 68, 13 ; free
mcall -1 ; close thread
include 'parser.inc'
; DATA AND FUNCTION
include 'httpd_lib.inc'
; DATA
srv_backlog: dd 0 ; максимум одновременных подключений подключений
srv_socket: dd 0
srv_sockaddr:
dw AF_INET4
.port dw 0
.ip dd 0
rb 8
.length = $ - srv_sockaddr
GLOBAL_DATA:
.units dd 0 ; указатель на ассоциативный массив пути и указателя на функцию либы(см ниж)
.unit_count dd 0 ; количество записей в массиве
.libs dd 0 ; указатель на массив указателей на ассоциативные массивы библиотек
;; .flags dd 0 ; 1 - all hosts(элемент hosts не указатель на массив, а на функцию)

25
httpd.ini Normal file
View File

@@ -0,0 +1,25 @@
[MAIN]
# server IPv4 address. Non IPv6 address
ip=127.0.0.1
# server port number
port=80
# count open connection
conn=100
# 1000 - no parse http headers, raw data in CONNECT_DATA.message_body
# This flags using for http/2.0 and other protocol execution.
flags=0000 # parsing http headers
# directory for find files
work_dir=/sys/http_server/data
# directory for find lib
units_dir=/sys/http_units
[UNITS]
# list units
# path = path to lib in units_dir
database/sqlite3=sqlite3_serv.obj
database/cvs=cvs_table_server.obj
# server called function httpd_unit_func(CONNECT_DATA* struct_server );
# for init unit, server called function httpd_unit_init(void* global_data);

109
httpd_lib.inc Normal file
View File

@@ -0,0 +1,109 @@
align 4
day:
dd 'Mon,'
dd 'Tue,'
dd 'Wed,'
dd 'Thu,'
dd 'Fri,'
dd 'Sat,'
dd 'Sun,'
.count = ($ - day) / 4
align 4
months:
dd 'Jan '
dd 'Feb '
dd 'Mar '
dd 'Apr '
dd 'May '
dd 'Jun '
dd 'Jul '
dd 'Aug '
dd 'Sep '
dd 'Oct '
dd 'Nov '
dd 'Dec '
.count = ($ - months) / 4 ; count item in this array
; HTTP-date = rfc1123-date | rfc850-date | asctime-date
; rfc1123-date = wkday "," SP date1 SP time SP "GMT"
; rfc850-date = weekday "," SP date2 SP time SP "GMT"
; asctime-date = wkday SP date3 SP time SP 4DIGIT
; date1 = 2DIGIT SP month SP 4DIGIT
; ; day month year (e.g., 02 Jun 1982)
; date2 = 2DIGIT "-" month "-" 2DIGIT
; ; day-month-year (e.g., 02-Jun-82)
; date3 = month SP ( 2DIGIT | ( SP 1DIGIT ))
; ; month day (e.g., Jun 2)
; time = 2DIGIT ":" 2DIGIT ":" 2DIGIT
; ; 00:00:00 - 23:59:59
; wkday = "Mon" | "Tue" | "Wed"
; | "Thu" | "Fri" | "Sat" | "Sun"
; weekday = "Monday" | "Tuesday" | "Wednesday"
; | "Thursday" | "Friday" | "Saturday" | "Sunday"
; month = "Jan" | "Feb" | "Mar" | "Apr"
; | "May" | "Jun" | "Jul" | "Aug"
; | "Sep" | "Oct" | "Nov" | "Dec"
serv_header:
.Accept_Ranges db 'Accept-Ranges: bytes',13,10
.connection db 'Connection: close',0
http_method:
.get: db 'GET '
.head: db 'HEAD'
.post: db 'POST'
.put: db 'PUT '
.patch db 'PATCH'
;error_404:
; db '<html>'
; db '<title>Error 404</title>'
; db '<center>'
; db 'Error 404 <br>'
; db 'The server could not find the requested page.<br><br>'
; db '</center>'
; db '</html>',0
http_err_response:
db 'HTTP/1.1 '
.code = $ - http_err_response
db '000 ',13, 10
db 'Server: simple-httpd/0.0.1', 13, 10
db 'Date: '
.date = $ - http_err_response
db 'Sun, 30 Oct 2022 09:29:13 GMT',13, 10
db 'Content-length: 0', 13, 10
db 'Content-type: text/plain', 13, 10;
db 'Connection: close', 13, 10
db 13, 10
.size = $ - http_err_response
base_response:
label response at 0
db 'HTTP/1.0 '
.code: db '000 ',13, 10
db 'Server: httpd(kolibri os)/0.0.1', 13, 10
db 'Cache-Control: no-cache', 13, 10
db 'Content-Encoding: '
.content_encod: db 'identity', 13, 10
db 'Date: '
.date: db 'Sun, 30 Oct 2022 09:29:13 GMT',13, 10
db 'Content-length: '
.content_len: db ' ', 13, 10
db 'Content-type: '
.content_type: db ' ', 13, 10;
;'text/html; charset=utf-8'
.end_headers: ;нужно, когда базового заголовка не хватает
.connection db 'Connection: close', 13, 10
db 13, 10
.body: ; с этого оффсета уже писать данные
; min HTTP request size
; "GET / HTTP/1.1" - 18 byte
min_http_size = 18

254
parser.inc Normal file
View File

@@ -0,0 +1,254 @@
BASE_ARRAY_ARGS equ (esi - 1024)
BASE_ARRAY_HEADERS equ (esi - 2048)
; IN:
; esi - struct
; ecx = ptr to str URI
; OUT:
; ecx - new base for reading data ('HTTP/1.1 ...')
; eax -
; NOTE: this function don`t check buffer size
parse_url:
; URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
;
; hier-part = "//" authority path-abempty
; / path-absolute
; / path-rootless
; / path-empty
;
; foo://example.com:8042/over/there?name=ferret#nose
; \_/ \______________/\_________/ \_________/ \__/
; | | | | |
; scheme authority path query fragment
; | _____________________|__
; / \ / \
; urn:example:animal:ferret:nose
cmp byte[ecx], '/' ; check abs-path
je .get_path
;get scheme
mov [esi + CONNECT_DATA.uri_scheme], ecx
@@:
inc ecx
cmp byte[ecx - 1], ':'
jne @b
mov byte[ecx - 1], 0
cmp word[ecx], '//'
jne .get_path
add ecx, 2
mov [esi + CONNECT_DATA.uri_authority], ecx
;get authority
@@:
inc ecx
;cmp byte[ecx - 1], ' ' ;check end, не нужно, так как в http всегда / абс путь
cmp byte[ecx - 1], '/'
jne @b
dec ecx
.get_path:
;path-absolute
mov [esi + CONNECT_DATA.uri_path], ecx
@@:
inc ecx
cmp byte[ecx], '?'
je .get_query
cmp byte[ecx], '#'
je get_fragment
cmp byte[ecx], ' ' ; check end path
jne @b
mov byte[ecx], 0
inc ecx
jmp .exit
.get_query:
mov byte[ecx], 0
inc ecx
lea eax, [BASE_ARRAY_ARGS]
mov dword[esi + CONNECT_DATA.uri_arg], eax
xor edx, edx ; counter items
; add new item
.get_query_new_arg:
inc edx
mov dword[BASE_ARRAY_ARGS + (edx-1)*8], ecx
mov [esi + CONNECT_DATA.num_uri_args], edx
dec ecx
@@:
inc ecx
cmp byte[ecx], '='
je .get_args
cmp byte[ecx], '#' ; ЭТО БРЕД ПОЛНЫЙ, НО ВДРУГ
je .get_fragment
cmp byte[ecx], ' ' ; http://cjkhr.bvgbfdkvdf.dmejfgehf/?1pr1
jne @b
.exit_2:
mov byte[ecx], 0
inc ecx
jmp .exit
.get_args:
mov byte[ecx], 0
inc ecx
mov dword[BASE_ARRAY_ARGS + (edx-1)*8 + 4], ecx
dec ecx
@@:
inc ecx
cmp byte[ecx], '#'
je .get_fragment
cmp byte[ecx], ' '
je .exit_2
cmp byte[ecx] '&'
jne @b
jmp .get_query_new_arg
.get_fragment:
cmp byte[ecx], '#'
jne .exit
mov byte[ecx], 0
inc ecx
mov [esi + CONNECT_DATA.uri_fragment], ecx
@@:
inc ecx
cmp byte[ecx - 1], ' '
jne @b
mov [ecx - 1], 0
.exit:
ret
; IN:
; esi - struct
; ecx - ptr to begin headers block
; edx - free mem ptr
; OUT:
; ecx - new base for reading body message HTTP query
; eax -
; NOTE: this function don`t check buffer size
parse_headers:
; init array
mov [esi + CONNECT_DATA.num_headers], 0
mov [esi + CONNECT_DATA.http_headers], BASE_ARRAY_HEADERS
xor edx, edx
.new_str:
cmp word[ecx], 0x0A0D ; \n
jnz .find_header
; end find heeaders
add ecx, 2 ; ecx = base for body message
ret
.find_header:
; add new item in array headers
inc edx
mov dword[esi + CONNECT_DATA.num_headers], edx
; save pointer to name header
mov dword[BASE_ARRAY_HEADERS + (edx-1)*8], ecx
dec ecx
@@:
inc ecx
cmp byte[ecx], ':'
jnz @b
mov byte[ecx], 0 ; \0
inc ecx
; save pointer to value
mov dword[BASE_ARRAY_HEADERS + (edx-1)*8 + 4], ecx
@@:
inc ecx
cmp byte[ecx - 1], 0x0A0D
jnz @b
mov byte[ecx - 1], 0
inc ecx ; set offset on new string
jmp .new_str
; IN:
; ecx - raw data query
; esi - ptr to CONNECT_DATA
; OUT: eax = 0 error
; eax = prt to struct CONNECT_DATA
parse_http_query:
;method scheme://host:port/abs_path HTTP/1.1 0x0d 0x0a
;header_1:value 0x0d 0x0a
; ...
;header_N:value 0x0d 0x0a
;0xd 0xa
; message data
mov eax, [esi + CONNECT_DATA.request_size]
mov [esi + CONNECT_DATA.tmp_req_size], eax
; check size
cmp dword[esi + CONNECT_DATA.request_size], min_http_size
jb .error_exit
; get http METHOD this message
mov [esi + CONNECT_DATA.http_method], ecx
@@:
inc ecx
cmp byte[ecx - 1], ' ' ; find end method
jnz @b
mov byte[ecx - 1], 0
; check size
mov edx, ecx
sub edx, [esi + CONNECT_DATA.buffer_request]
sub edx, 2 ; / 0x20
cmp dword[esi + CONNECT_DATA.request_size], edx
jle .error_exit
; ecx <- uri string
; парсинг uri строки в заголовке запроса(получение схемы, пути аргументов, фрагмента и тд)
call parse_url
; check size
mov edx, ecx
sub edx, [esi + CONNECT_DATA.buffer_request]
sub edx, 7 ; H/0.0 0x0d0 x0a
cmp dword[esi + CONNECT_DATA.request_size], edx
jle .error_exit
; get http version(HTTP/1.1)
mov [esi + CONNECT_DATA.http_verion], ecx
@@:
inc ecx
cmp word[ecx - 1], 0x0A0D
jnz @b
mov word[ecx - 1], 0
inc ecx ; <- start first header
; check size
mov edx, ecx
sub edx, [esi + CONNECT_DATA.buffer_request]
sub edx, 2 ; 0x0d 0x0a
cmp dword[esi + CONNECT_DATA.request_size], edx
jle .error_exit
; получение заголовков запроса (ключ + значение-строка)
call parse_headers
; check size
mov edx, ecx
sub edx, [esi + CONNECT_DATA.buffer_request]
cmp dword[esi + CONNECT_DATA.request_size], edx
jl .error_exit
mov [esi + CONNECT_DATA.message_body], ecx
; докачивается всё остальное
mov eax, esi
ret
.error_exit:
xor eax, eax
ret

51
settings.inc Normal file
View File

@@ -0,0 +1,51 @@
struct CONNECT_DATA ; 16*4 = 64 bytes
socket dd 0 ; номер сокета подключения
sockaddr dd 16/4 ; socaddr connection
buffer_request dd 0 ; pointer to buffer for geting message socket
request_size dd 0 ; size geted data from client
tmp_req_size dd 0 ; для парсера
buffer_response dd 0 ; pointer to buffwr for resp message
http_method dd 0 ; указатель на строку
http_verion dd 0 ; указатель на строку
num_headers dd 0 ; number items in REQUEST_DATA
http_headers dd 0 ; указатель на массив REQUEST_DATA
uri_scheme dd 0 ; указатель на схему
uri_authority dd 0 ; pointer to struct ?
uri_path dd 0 ; указатель на декодированный путь к ресурсу(без параметров)
num_uri_args dd 0 ;
uri_arg dd 0 ; pointer to array REQUEST_DATA аргументов uri строк
uri_fragment dd 0 ; указатель на строку
message_body dd 0 ; указатель на тело http запроса
ends
struct REQUEST_DATA
ptr_name dd 0 ;
ptr_data dd 0 ;
ends
; Load server config
; ecx - path to file
; OUT: eax - 0 or err_code
load_settings:
; check file path
sub esp, 40 ; size file info struct
push esp
push ecx
call FileInfo
lea esp, [esp + 40]
test eax, eax
jnz .err
ret
.err:
ret
; Config format:
; Standart INI file:
; - ";" or "#" comments
; - [name] name of group
; - arg=val params in group

90
sys_func.inc Normal file
View File

@@ -0,0 +1,90 @@
; ABSTRACT SYSTEM FUNCTIONS
; stdcall socket(uint32_t domain, type, proto)
netfunc_socket:
push ebx esi
mcall 75, 0, [esp + 2*4 + 4], [esp + 2*4 + 8], [esp + 2*4 + 12]
;mov [fs:0], ebx ;errno
pop esi ebx
ret 12
;stdcall close(uint32_t sock_number);
netfunc_close:
push ebx
call 75, 1, [esp + 4 + 4]
pop ebx
ret 4
;stdcall bind(uint32_t sock_num, sockaddr* _sockaddr_struct, uint32_t sockaddr_size)
netfunc_bind:
push esi ebx
mcall 75, 2, [esp + 2*4 + 4], [esp + 2*4 + 8], [esp + 2*4 + 12]
pop ebx esi
ret 12
;stdcall listen(uint32_t socket, uint32_t backlog)
netfunc_listen:
push ebx
mcall 75, 3, [esp + 4 + 4], [esp + 4 + 8]
pop ebx
ret 8
;stdcall accept(uint32_t socket, sockaddr* new_sockaddr_struct, uint32_t sockaddr_size)
netfunc_accept:
push esi ebx
mcall 75, 5, [esp + 2*4 + 4], [esp + 2*4 + 8], [esp + 2*4 + 12]
pop ebx esi
ret 12
;stdcall send(uint32_t socket, void* buff, uint32_t len_buff, uint32_t flags)
netfunc_send:
push esi edi ebx
mcall 75, 6, [esp + 3*4 + 4], [esp + 3*4 + 8],\
[esp + 3*4 + 12], [esp + 3*4 + 16]
pop ebx edi esi
ret 16
;stdcall recv(uint32_t socket, void* buff, uint32_t len_buff, uint32_t flags)
netfunc_recv:
push esi edi ebx
mcall 75, 7, [esp + 3*4 + 4], [esp + 3*4 + 8],\
[esp + 3*4 + 12], [esp + 3*4 + 16]
pop ebx edi esi
ret 16
; stdcall CreatThread(void* entry_thread);
CreateThread:
push ebx edi
mcall 68, 12, 0x4000 ;alloc memory 16 kib for stack
test eax, eax
jz .err
mov ecx, 0x4000/4
mov edi, eax
;start thread for new connection
mov edx, eax
xor eax, eax
rep stosd
add edx, 0x4000
mcall 51, 1, [esp + 2*4 + 4] ;<- thread entry
.err:
pop ebx edi
ret 4
; stdcall Alloc(uint32_t size)
Alloc:
push ebx
mcall 68, 12, [esp + 4 + 4]
pop ebx
ret 4
; stdcall Free(void* ptr)
Free:
push ebx
mcall 68, 13, [esp + 4 + 4]
pop ebx
ret 4
;NTSTATUS stdcall FileInfo(const char* path, void* buff)
FileInfo:
ret 8
;NTSATTUS stdcall FileRead(const char* path, void* buff, uint32_t size)
FileRead:
ret 12