2019-10-24 19:44:49 +03: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 19:21:36 +03:00
module vweb
import (
os
2019-10-24 19:44:49 +03:00
net
http
net . urllib
)
2019-07-29 19:21:36 +03:00
2019-08-10 10:12:17 +03:00
const (
methods_with_form = [ ' P O S T ' , ' P U T ' , ' P A T C H ' ]
2019-09-05 15:46:24 +03: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 10:12:17 +03:00
)
2019-10-24 19:44:49 +03: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-09-05 15:46:24 +03:00
}
pub fn ( ctx Context ) html ( html string ) {
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 ' )
}
2019-07-30 16:46:10 +03:00
pub fn ( ctx Context ) text ( s string ) {
2019-09-05 15:46:24 +03: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 / p l a i n \r \n $ ctx . headers \r \n \r \n $ s ' )
}
2019-07-29 19:21:36 +03:00
2019-07-30 06:13:44 +03:00
pub fn ( ctx Context ) json ( s string ) {
2019-10-24 19:44:49 +03: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 ' )
2019-09-05 15:46:24 +03:00
}
2019-07-29 19:21:36 +03:00
pub fn ( ctx Context ) redirect ( url string ) {
2019-10-24 19:44:49 +03: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 \r \n $ ctx . headers ' )
2019-09-05 15:46:24 +03:00
}
2019-07-29 19:21:36 +03:00
2019-08-02 05:04:48 +03:00
pub fn ( ctx Context ) not_found ( s string ) {
2019-09-05 15:46:24 +03:00
ctx . conn . write ( HTTP_404 )
}
2019-08-02 05:04:48 +03:00
2019-09-05 15:46:24 +03: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)
ctx . add_header ( ' S e t - C o o k i e ' , ' $ key = $ val ' )
}
2019-07-29 19:21:36 +03:00
2019-09-05 15:46:24 +03:00
pub fn ( ctx mut Context ) get_cookie ( key string ) ? string { // TODO refactor
cookie_header := ctx . get_header ( ' C o o k i e ' )
cookie := if cookie_header . contains ( ' ; ' ) {
cookie_header . find_between ( ' $ key = ' , ' ; ' )
} else {
cookie_header
}
if cookie != ' ' {
return cookie
}
return error ( ' C o o k i e n o t f o u n d ' )
}
2019-07-29 19:21:36 +03:00
2019-09-05 15:46:24 +03:00
fn ( ctx mut Context ) add_header ( key , val string ) {
ctx . headers = ctx . headers + if ctx . headers == ' ' { ' $ key : v a l ' } else { ' \r \n $ key : v a l ' }
2019-07-29 19:21:36 +03:00
}
2019-09-05 15:46:24 +03:00
fn ( ctx mut Context ) get_header ( key string ) string {
return ctx . headers . find_between ( ' \r \n $ key : ' , ' \r \n ' )
}
2019-07-29 19:21:36 +03:00
2019-10-24 19:44:49 +03:00
//pub fn run<T>(port int) {
pub fn run < T > ( app T , port int ) {
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 19:21:36 +03:00
for {
conn := l . accept ( ) or {
2019-09-05 15:46:24 +03:00
panic ( ' a c c e p t ( ) f a i l e d ' )
2019-10-24 19:44:49 +03:00
}
2019-09-05 15:46:24 +03:00
//foobar<T>()
2019-07-29 19:21:36 +03:00
// TODO move this to handle_conn<T>(conn, app)
2019-08-11 15:07:22 +03:00
s := conn . read_line ( )
if s == ' ' {
2019-09-05 15:46:24 +03:00
conn . write ( HTTP_500 )
2019-08-11 15:07:22 +03:00
conn . close ( )
2019-09-05 15:46:24 +03:00
return
2019-08-11 15:07:22 +03:00
}
2019-07-29 19:21:36 +03:00
// Parse the first line
// "GET / HTTP/1.1"
first_line := s . all_before ( ' \n ' )
2019-09-05 15:46:24 +03:00
vals := first_line . split ( ' ' )
if vals . len < 2 {
println ( ' n o v a l s f o r h t t p ' )
conn . write ( HTTP_500 )
conn . close ( )
return
}
mut action := vals [ 1 ] . right ( 1 ) . all_before ( ' / ' )
2019-08-01 18:57:01 +03:00
if action . contains ( ' ? ' ) {
2019-09-05 15:46:24 +03:00
action = action . all_before ( ' ? ' )
}
2019-07-29 19:21:36 +03:00
if action == ' ' {
2019-09-05 15:46:24 +03:00
action = ' i n d e x '
}
2019-07-29 19:21:36 +03:00
req := http . Request {
2019-09-05 15:46:24 +03:00
headers : http . parse_headers ( s . split_into_lines ( ) )
2019-08-11 15:07:22 +03:00
ws_func : 0
user_ptr : 0
method : vals [ 0 ]
2019-10-24 19:44:49 +03:00
url : vals [ 1 ]
}
2019-08-20 11:18:12 +03:00
$ if debug {
println ( ' v w e b a c t i o n = " $ action " ' )
}
2019-08-03 02:35:36 +03:00
//mut app := T{
app . vweb = Context {
2019-10-24 19:44:49 +03:00
req : req
conn : conn
2019-08-17 02:55:11 +03:00
form : map [ string ] string
static_files : map [ string ] string
static_mime_types : map [ string ] string
2019-10-24 19:44:49 +03:00
}
//}
2019-08-10 10:12:17 +03:00
if req . method in methods_with_form {
2019-10-24 19:44:49 +03:00
app . vweb . parse_form ( s )
}
2019-07-29 19:21:36 +03:00
if vals . len < 2 {
2019-08-20 11:18:12 +03:00
$ if debug {
println ( ' n o v a l s f o r h t t p ' )
}
2019-08-02 05:04:48 +03:00
conn . close ( )
2019-10-24 19:44:49 +03:00
continue
}
2019-07-31 07:10:53 +03:00
2019-10-24 19:44:49 +03:00
// Serve a static file if it's one
2019-07-31 07:10:53 +03:00
// if app.vweb.handle_static() {
// conn.close()
2019-10-24 19:44:49 +03:00
// continue
2019-09-05 15:46:24 +03:00
// }
2019-07-31 07:10:53 +03:00
2019-09-05 15:46:24 +03:00
// Call the right action
app . $ action ( ) or {
conn . write ( HTTP_404 )
2019-08-11 15:07:22 +03:00
}
2019-07-29 19:21:36 +03:00
conn . close ( )
}
2019-09-05 15:46:24 +03:00
}
2019-07-29 19:21:36 +03:00
2019-10-24 19:44:49 +03:00
pub fn foobar < T > ( ) {
}
2019-08-13 14:50:19 +03:00
2019-10-24 19:44:49 +03:00
fn ( ctx mut Context ) parse_form ( s string ) {
2019-08-10 10:12:17 +03:00
if ! ( ctx . req . method in methods_with_form ) {
2019-10-24 19:44:49 +03:00
return
}
2019-07-29 19:21:36 +03:00
pos := s . index ( ' \r \n \r \n ' )
if pos > - 1 {
mut str_form := s . substr ( pos , s . len )
str_form = str_form . replace ( ' + ' , ' ' )
words := str_form . split ( ' & ' )
for word in words {
2019-08-20 11:18:12 +03:00
$ if debug {
2019-10-24 19:44:49 +03:00
println ( ' p a r s e f o r m k e y v a l = " $ word " ' )
2019-08-20 11:18:12 +03:00
}
2019-10-24 19:44:49 +03:00
keyval := word . trim_space ( ) . split ( ' = ' )
if keyval . len != 2 { continue }
2019-07-29 19:21:36 +03:00
key := keyval [ 0 ]
2019-08-12 18:54:28 +03:00
val := urllib . query_unescape ( keyval [ 1 ] ) or {
2019-10-24 19:44:49 +03:00
continue
2019-08-20 11:18:12 +03:00
}
2019-10-24 19:44:49 +03:00
$ if debug {
println ( ' h t t p f o r m " $ key " = > " $ val " ' )
2019-08-20 11:18:12 +03:00
}
2019-10-24 19:44:49 +03:00
ctx . form [ key ] = val
2019-07-29 19:21:36 +03:00
}
}
2019-09-05 15:46:24 +03:00
}
2019-07-29 19:21:36 +03:00
2019-07-31 07:10:53 +03:00
fn ( ctx mut Context ) scan_static_directory ( directory_path , mount_path string ) {
2019-10-17 14:30:05 +03:00
files := os . ls ( directory_path ) or { panic ( err ) }
2019-07-31 07:10:53 +03:00
if files . len > 0 {
2019-09-05 15:46:24 +03:00
for file in files {
2019-07-31 07:10:53 +03:00
mut ext := ' '
mut i := file . len
mut flag := true
for i > 0 {
i --
if flag {
ext = file . substr ( i , i + 1 ) + ext
}
if file . substr ( i , i + 1 ) == ' . ' {
flag = false
}
}
2019-09-05 15:46:24 +03:00
// todo: os.is_dir is broken now so we expect that file is dir it has no extension
2019-07-31 07:10:53 +03:00
if flag {
ctx . scan_static_directory ( directory_path + ' / ' + file , mount_path + ' / ' + file )
} else {
2019-09-05 15:46:24 +03:00
ctx . static_files [ mount_path + ' / ' + file ] = directory_path + ' / ' + file
2019-07-31 07:10:53 +03:00
ctx . static_mime_types [ mount_path + ' / ' + file ] = mime_types [ ext ]
}
}
}
}
2019-09-05 15:46:24 +03:00
pub fn ( ctx mut Context ) handle_static ( directory_path string ) bool {
2019-07-31 07:10:53 +03:00
ctx . scan_static_directory ( directory_path , ' ' )
2019-09-05 15:46:24 +03:00
static_file := ctx . static_files [ ctx . req . url ]
2019-07-31 07:10:53 +03:00
mime_type := ctx . static_mime_types [ ctx . req . url ]
2019-10-24 19:44:49 +03:00
if static_file != ' ' {
data := os . read_file ( static_file ) or { return false }
2019-09-05 15:46:24 +03: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 ' )
2019-10-24 19:44:49 +03:00
return true
}
return false
}
2019-07-30 16:46:10 +03:00
2019-09-05 15:46:24 +03:00
pub fn ( ctx mut Context ) serve_static ( url , file_path , mime_type string ) {
2019-10-24 19:44:49 +03:00
ctx . static_files [ url ] = file_path
2019-07-31 07:10:53 +03:00
ctx . static_mime_types [ url ] = mime_type
2019-08-11 15:07:22 +03:00
}