2019-10-24 18:44:49 +02:00
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
2019-07-29 18:21:36 +02:00
module vweb
import (
os
2019-10-24 18:44:49 +02:00
net
http
net . urllib
)
2019-07-29 18:21:36 +02:00
2019-08-10 09:12:17 +02:00
const (
methods_with_form = [ ' P O S T ' , ' P U T ' , ' P A T C H ' ]
2019-09-05 14:46:24 +02:00
HEADER_SERVER = ' S e r v e r : V W e b \r \n ' // TODO add to the headers
HTTP_404 = ' H T T P / 1 . 1 4 0 4 N o t F o u n d \r \n C o n t e n t - T y p e : t e x t / p l a i n \r \n \r \n 4 0 4 N o t F o u n d '
HTTP_500 = ' H T T P / 1 . 1 5 0 0 I n t e r n a l S e r v e r E r r o r \r \n C o n t e n t - T y p e : t e x t / p l a i n \r \n \r \n 5 0 0 I n t e r n a l S e r v e r E r r o r '
mime_types = {
' . c s s ' : ' t e x t / c s s ; c h a r s e t = u t f - 8 ' ,
' . g i f ' : ' i m a g e / g i f ' ,
' . h t m ' : ' t e x t / h t m l ; c h a r s e t = u t f - 8 ' ,
' . h t m l ' : ' t e x t / h t m l ; c h a r s e t = u t f - 8 ' ,
' . j p g ' : ' i m a g e / j p e g ' ,
' . j s ' : ' a p p l i c a t i o n / j a v a s c r i p t ' ,
' . w a s m ' : ' a p p l i c a t i o n / w a s m ' ,
' . p d f ' : ' a p p l i c a t i o n / p d f ' ,
' . p n g ' : ' i m a g e / p n g ' ,
' . s v g ' : ' i m a g e / s v g + x m l ' ,
' . x m l ' : ' t e x t / x m l ; c h a r s e t = u t f - 8 '
}
2019-08-10 09:12:17 +02:00
)
2019-10-24 18:44:49 +02:00
pub struct Context {
static_files map [ string ] string
static_mime_types map [ string ] string
pub :
req http . Request
conn net . Socket
form map [ string ] string
// TODO Response
mut :
headers string // response headers
2019-12-09 15:10:44 +01:00
done bool
2019-09-05 14:46:24 +02:00
}
pub fn ( ctx Context ) html ( html string ) {
2019-12-09 15:10:44 +01:00
if ctx . done { return }
2019-12-06 22:44:22 +01:00
//println('HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n$ctx.headers\r\n\r\n$html')
2019-11-22 06:22:11 +01:00
ctx . conn . write ( ' H T T P / 1 . 1 2 0 0 O K \r \n C o n t e n t - T y p e : t e x t / h t m l \r \n $ ctx . headers \r \n \r \n $ html ' ) or { panic ( err ) }
2019-09-05 14:46:24 +02:00
}
2019-07-30 15:46:10 +02:00
2019-12-09 15:10:44 +01:00
pub fn ( ctx mut Context ) text ( s string ) {
if ctx . done { return }
ctx . conn . write ( ' H T T P / 1 . 1 2 0 0 O K \r \n C o n t e n t - T y p e : t e x t / p l a i n \r \n ' +
' $ ctx . headers \r \n $ s ' ) or { panic ( err ) }
ctx . done = true
2019-09-05 14:46:24 +02:00
}
2019-07-29 18:21:36 +02:00
2019-07-30 05:13:44 +02:00
pub fn ( ctx Context ) json ( s string ) {
2019-12-09 15:10:44 +01:00
if ctx . done { return }
2019-11-22 06:22:11 +01:00
ctx . conn . write ( ' H T T P / 1 . 1 2 0 0 O K \r \n C o n t e n t - T y p e : a p p l i c a t i o n / j s o n \r \n $ ctx . headers \r \n \r \n $ s ' ) or { panic ( err ) }
2019-09-05 14:46:24 +02:00
}
2019-07-29 18:21:36 +02:00
pub fn ( ctx Context ) redirect ( url string ) {
2019-12-09 15:10:44 +01:00
if ctx . done { return }
2019-12-07 23:05:57 +01:00
ctx . conn . write ( ' H T T P / 1 . 1 3 0 2 F o u n d \r \n L o c a t i o n : $ url \r \n $ ctx . headers \r \n \r \n ' ) or { panic ( err ) }
2019-09-05 14:46:24 +02:00
}
2019-07-29 18:21:36 +02:00
2019-08-02 04:04:48 +02:00
pub fn ( ctx Context ) not_found ( s string ) {
2019-12-09 15:10:44 +01:00
if ctx . done { return }
2019-11-22 06:22:11 +01:00
ctx . conn . write ( HTTP_404 ) or { panic ( err ) }
2019-09-05 14:46:24 +02:00
}
2019-08-02 04:04:48 +02:00
2019-09-05 14:46:24 +02:00
pub fn ( ctx mut Context ) set_cookie ( key , val string ) { // TODO support directives, escape cookie value (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie)
2019-12-06 13:24:53 +01:00
//println('Set-Cookie $key=$val')
2019-09-05 14:46:24 +02:00
ctx . add_header ( ' S e t - C o o k i e ' , ' $ key = $ val ' )
}
2019-07-29 18:21:36 +02:00
2019-12-06 13:24:53 +01:00
pub fn ( ctx & Context ) get_cookie ( key string ) ? string { // TODO refactor
2019-12-07 23:48:49 +01:00
cookie_header := ' ' + ctx . get_header ( ' C o o k i e ' )
2019-09-05 14:46:24 +02:00
cookie := if cookie_header . contains ( ' ; ' ) {
2019-12-07 23:48:49 +01:00
cookie_header . find_between ( ' $ key = ' , ' ; ' )
2019-09-05 14:46:24 +02:00
} else {
2019-12-07 23:48:49 +01:00
cookie_header . find_between ( ' $ key = ' , ' \r ' )
2019-09-05 14:46:24 +02:00
}
if cookie != ' ' {
2019-12-07 23:48:49 +01:00
return cookie . trim_space ( )
2019-09-05 14:46:24 +02:00
}
return error ( ' C o o k i e n o t f o u n d ' )
}
2019-07-29 18:21:36 +02:00
2019-09-05 14:46:24 +02:00
fn ( ctx mut Context ) add_header ( key , val string ) {
2019-12-06 13:24:53 +01:00
//println('add_header($key, $val)')
ctx . headers = ctx . headers +
if ctx . headers == ' ' { ' $ key : $ val ' } else { ' \r \n $ key : $ val ' }
//println(ctx.headers)
2019-07-29 18:21:36 +02:00
}
2019-12-06 13:24:53 +01:00
fn ( ctx & Context ) get_header ( key string ) string {
2019-12-06 22:44:22 +01:00
return ctx . req . headers [ key ]
2019-09-05 14:46:24 +02:00
}
2019-07-29 18:21:36 +02:00
2019-10-24 18:44:49 +02:00
//pub fn run<T>(port int) {
2019-11-22 06:22:11 +01:00
pub fn run < T > ( app mut T , port int ) {
2019-10-24 18:44:49 +02:00
println ( ' R u n n i n g v w e b a p p o n h t t p : / / l o c a l h o s t : $ port . . . ' )
l := net . listen ( port ) or { panic ( ' f a i l e d t o l i s t e n ' ) }
//mut app := T{}
app . init ( )
2019-07-29 18:21:36 +02:00
for {
conn := l . accept ( ) or {
2019-09-05 14:46:24 +02:00
panic ( ' a c c e p t ( ) f a i l e d ' )
2019-10-24 18:44:49 +02:00
}
2019-09-05 14:46:24 +02:00
//foobar<T>()
2019-07-29 18:21:36 +02:00
// TODO move this to handle_conn<T>(conn, app)
2019-12-06 22:44:22 +01:00
first_line := conn . read_line ( )
if first_line == ' ' {
2019-11-22 06:22:11 +01:00
conn . write ( HTTP_500 ) or { }
conn . close ( ) or { }
2019-09-05 14:46:24 +02:00
return
2019-08-11 14:07:22 +02:00
}
2019-07-29 18:21:36 +02:00
// Parse the first line
// "GET / HTTP/1.1"
2019-12-06 22:44:22 +01:00
//first_line := s.all_before('\n')
2019-09-05 14:46:24 +02:00
vals := first_line . split ( ' ' )
if vals . len < 2 {
println ( ' n o v a l s f o r h t t p ' )
2019-11-22 06:22:11 +01:00
conn . write ( HTTP_500 ) or { }
conn . close ( ) or { }
2019-09-05 14:46:24 +02:00
return
}
2019-12-06 22:44:22 +01:00
mut headers := [ ] string
for _ in 0 .. 30 {
header := conn . read_line ( )
headers << header
//println('header="$header" len = ' + header.len.str())
if header . len <= 2 {
break
}
}
2019-10-27 08:03:15 +01:00
mut action := vals [ 1 ] [ 1 .. ] . all_before ( ' / ' )
2019-08-01 17:57:01 +02:00
if action . contains ( ' ? ' ) {
2019-09-05 14:46:24 +02:00
action = action . all_before ( ' ? ' )
}
2019-07-29 18:21:36 +02:00
if action == ' ' {
2019-09-05 14:46:24 +02:00
action = ' i n d e x '
}
2019-07-29 18:21:36 +02:00
req := http . Request {
2019-12-06 22:44:22 +01:00
headers : http . parse_headers ( headers ) //s.split_into_lines())
2019-08-11 14:07:22 +02:00
ws_func : 0
user_ptr : 0
method : vals [ 0 ]
2019-10-24 18:44:49 +02:00
url : vals [ 1 ]
}
2019-08-20 10:18:12 +02:00
$ if debug {
2019-12-06 22:44:22 +01:00
println ( ' r e q . h e a d e r s = ' )
println ( req . headers )
2019-08-20 10:18:12 +02:00
println ( ' v w e b a c t i o n = " $ action " ' )
}
2019-08-03 01:35:36 +02:00
//mut app := T{
app . vweb = Context {
2019-10-24 18:44:49 +02:00
req : req
conn : conn
2019-08-17 01:55:11 +02:00
form : map [ string ] string
static_files : map [ string ] string
static_mime_types : map [ string ] string
2019-10-24 18:44:49 +02:00
}
//}
2019-08-10 09:12:17 +02:00
if req . method in methods_with_form {
2019-12-08 15:37:23 +01:00
/ *
2019-11-26 11:54:41 +01:00
for {
line := conn . read_line ( )
if line == ' ' || line == ' \r \n ' {
break
2019-12-06 13:24:53 +01:00
}
2019-11-26 11:54:41 +01:00
//if line.contains('POST') || line == '' {
//break
2019-12-06 13:24:53 +01:00
//}
}
2019-12-08 15:37:23 +01:00
* /
2019-11-26 11:54:41 +01:00
line := conn . read_line ( )
app . vweb . parse_form ( line )
2019-10-24 18:44:49 +02:00
}
2019-07-29 18:21:36 +02:00
if vals . len < 2 {
2019-08-20 10:18:12 +02:00
$ if debug {
println ( ' n o v a l s f o r h t t p ' )
}
2019-11-22 06:22:11 +01:00
conn . close ( ) or { }
2019-10-24 18:44:49 +02:00
continue
}
2019-07-31 06:10:53 +02:00
2019-10-24 18:44:49 +02:00
// Serve a static file if it's one
2019-07-31 06:10:53 +02:00
// if app.vweb.handle_static() {
// conn.close()
2019-10-24 18:44:49 +02:00
// continue
2019-09-05 14:46:24 +02:00
// }
2019-07-31 06:10:53 +02:00
2019-09-05 14:46:24 +02:00
// Call the right action
app . $ action ( ) or {
2019-11-22 06:22:11 +01:00
conn . write ( HTTP_404 ) or { }
2019-08-11 14:07:22 +02:00
}
2019-11-22 06:22:11 +01:00
conn . close ( ) or { }
2019-12-08 16:07:04 +01:00
reset := ' r e s e t '
app . $ reset ( )
2019-07-29 18:21:36 +02:00
}
2019-09-05 14:46:24 +02:00
}
2019-07-29 18:21:36 +02:00
2019-10-24 18:44:49 +02:00
pub fn foobar < T > ( ) {
}
2019-08-13 13:50:19 +02:00
2019-10-24 18:44:49 +02:00
fn ( ctx mut Context ) parse_form ( s string ) {
2019-08-10 09:12:17 +02:00
if ! ( ctx . req . method in methods_with_form ) {
2019-10-24 18:44:49 +02:00
return
}
2019-11-26 11:54:41 +01:00
//pos := s.index('\r\n\r\n')
//if pos > -1 {
mut str_form := s //[pos..s.len]
str_form = str_form . replace ( ' + ' , ' ' )
words := str_form . split ( ' & ' )
for word in words {
$ if debug {
println ( ' p a r s e f o r m k e y v a l = " $ word " ' )
}
keyval := word . trim_space ( ) . split ( ' = ' )
if keyval . len != 2 { continue }
key := keyval [ 0 ]
val := urllib . query_unescape ( keyval [ 1 ] ) or {
continue
}
$ if debug {
println ( ' h t t p f o r m " $ key " = > " $ val " ' )
2019-07-29 18:21:36 +02:00
}
2019-11-26 11:54:41 +01:00
ctx . form [ key ] = val
2019-07-29 18:21:36 +02:00
}
2019-11-26 11:54:41 +01:00
//}
2019-09-05 14:46:24 +02:00
}
2019-07-29 18:21:36 +02:00
2019-07-31 06:10:53 +02:00
fn ( ctx mut Context ) scan_static_directory ( directory_path , mount_path string ) {
2019-10-17 13:30:05 +02:00
files := os . ls ( directory_path ) or { panic ( err ) }
2019-07-31 06:10:53 +02:00
if files . len > 0 {
2019-09-05 14:46:24 +02:00
for file in files {
2019-07-31 06:10:53 +02:00
mut ext := ' '
mut i := file . len
mut flag := true
for i > 0 {
i --
if flag {
2019-10-27 08:03:15 +01:00
ext = file [ i .. i + 1 ] + ext
2019-07-31 06:10:53 +02:00
}
2019-10-27 08:03:15 +01:00
if file [ i .. i + 1 ] == ' . ' {
2019-07-31 06:10:53 +02:00
flag = false
}
}
2019-09-05 14:46:24 +02:00
// todo: os.is_dir is broken now so we expect that file is dir it has no extension
2019-11-22 08:55:20 +01:00
// if flag {
if os . is_dir ( file ) {
2019-07-31 06:10:53 +02:00
ctx . scan_static_directory ( directory_path + ' / ' + file , mount_path + ' / ' + file )
} else {
2019-09-05 14:46:24 +02:00
ctx . static_files [ mount_path + ' / ' + file ] = directory_path + ' / ' + file
2019-07-31 06:10:53 +02:00
ctx . static_mime_types [ mount_path + ' / ' + file ] = mime_types [ ext ]
}
}
}
}
2019-09-05 14:46:24 +02:00
pub fn ( ctx mut Context ) handle_static ( directory_path string ) bool {
2019-07-31 06:10:53 +02:00
ctx . scan_static_directory ( directory_path , ' ' )
2019-09-05 14:46:24 +02:00
static_file := ctx . static_files [ ctx . req . url ]
2019-07-31 06:10:53 +02:00
mime_type := ctx . static_mime_types [ ctx . req . url ]
2019-10-24 18:44:49 +02:00
if static_file != ' ' {
data := os . read_file ( static_file ) or { return false }
2019-11-22 06:22:11 +01:00
ctx . conn . write ( ' H T T P / 1 . 1 2 0 0 O K \r \n C o n t e n t - T y p e : $ mime_type \r \n \r \n $ data ' ) or { panic ( err ) }
2019-10-24 18:44:49 +02:00
return true
}
return false
}
2019-07-30 15:46:10 +02:00
2019-09-05 14:46:24 +02:00
pub fn ( ctx mut Context ) serve_static ( url , file_path , mime_type string ) {
2019-10-24 18:44:49 +02:00
ctx . static_files [ url ] = file_path
2019-07-31 06:10:53 +02:00
ctx . static_mime_types [ url ] = mime_type
2019-08-11 14:07:22 +02:00
}