From 46a3bcb8409224ea03d0ccf6ad2e1080441788b3 Mon Sep 17 00:00:00 2001 From: Mahmoud-Emad Date: Mon, 1 Sep 2025 13:00:17 +0300 Subject: [PATCH] refactor: Migrate from `vweb` to `veb` web framework - Update all references from `vweb` to `veb` - Add `veb.StaticHandler` to `Playground` struct - Ensure error propagation for static file serving calls - Apply consistent indentation across various module definitions - Adjust documentation and comments for `veb` framework --- aiprompts/v_advanced/net.md | 246 +++++++++--------- lib/dav/webdav/README.md | 24 +- lib/schemas/openrpc/playground.v | 15 +- lib/security/authentication/client.v | 2 +- lib/security/authentication/controller.v | 22 +- lib/security/jwt/jwt.v | 2 +- .../baobab/generator/templates/cli.v.template | 4 +- .../generator/templates/playground.v.template | 4 +- 8 files changed, 163 insertions(+), 156 deletions(-) diff --git a/aiprompts/v_advanced/net.md b/aiprompts/v_advanced/net.md index 58e7cc52..2ffb49c5 100644 --- a/aiprompts/v_advanced/net.md +++ b/aiprompts/v_advanced/net.md @@ -1,26 +1,26 @@ module net ## Description - + `net` provides networking functions. It is mostly a wrapper to BSD sockets, so you can listen on a port, connect to remote TCP/UDP services, and communicate with them. const msg_nosignal = 0x4000 const err_connection_refused = error_with_code('net: connection refused', errors_base + 10) const err_option_wrong_type = error_with_code('net: set_option_xxx option wrong type', - errors_base + 3) + errors_base + 3) const opts_can_set = [ - SocketOption.broadcast, - .debug, - .dont_route, - .keep_alive, - .linger, - .oob_inline, - .receive_buf_size, - .receive_low_size, - .receive_timeout, - .send_buf_size, - .send_low_size, - .send_timeout, - .ipv6_only, + SocketOption.broadcast, + .debug, + .dont_route, + .keep_alive, + .linger, + .oob_inline, + .receive_buf_size, + .receive_low_size, + .receive_timeout, + .send_buf_size, + .send_low_size, + .send_timeout, + .ipv6_only, ] const error_eagain = C.EAGAIN const err_port_out_of_range = error_with_code('net: port out of range', errors_base + 5) @@ -29,12 +29,12 @@ const err_connect_failed = error_with_code('net: connect failed', errors_base + const errors_base = 0 Well defined errors that are returned from socket functions const opts_int = [ - SocketOption.receive_buf_size, - .receive_low_size, - .receive_timeout, - .send_buf_size, - .send_low_size, - .send_timeout, + SocketOption.receive_buf_size, + .receive_low_size, + .receive_timeout, + .send_buf_size, + .send_low_size, + .send_timeout, ] const error_eintr = C.EINTR const error_ewouldblock = C.EWOULDBLOCK @@ -43,17 +43,17 @@ const error_einprogress = C.EINPROGRESS const err_timed_out_code = errors_base + 9 const err_connect_timed_out = error_with_code('net: connect timed out', errors_base + 8) const err_new_socket_failed = error_with_code('net: new_socket failed to create socket', - errors_base + 1) + errors_base + 1) const msg_dontwait = C.MSG_DONTWAIT const infinite_timeout = time.infinite infinite_timeout should be given to functions when an infinite_timeout is wanted (i.e. functions only ever return with data) const no_timeout = time.Duration(0) no_timeout should be given to functions when no timeout is wanted (i.e. all functions return instantly) const err_timed_out = error_with_code('net: op timed out', errors_base + 9) -const tcp_default_read_timeout = 30 * time.second +const tcp_default_read_timeout = 30 *time.second const err_option_not_settable = error_with_code('net: set_option_xxx option not settable', - errors_base + 2) -const tcp_default_write_timeout = 30 * time.second + errors_base + 2) +const tcp_default_write_timeout = 30* time.second fn addr_from_socket_handle(handle int) Addr addr_from_socket_handle returns an address, based on the given integer socket `handle` fn close(handle int) ! @@ -94,16 +94,16 @@ fn validate_port(port int) !u16 validate_port checks whether a port is valid and returns the port or an error fn wrap_error(error_code int) ! interface Connection { - addr() !Addr - peer_addr() !Addr + addr() !Addr + peer_addr() !Addr mut: - read(mut []u8) !int - write([]u8) !int - close() ! + read(mut []u8) !int + write([]u8) !int + close() ! } Connection provides a generic SOCK_STREAM style interface that protocols can use as a base connection object to support TCP, UNIX Domain Sockets and various proxying solutions. interface Dialer { - dial(address string) !Connection + dial(address string) !Connection } Dialer is an abstract dialer interface for producing connections to adresses. fn (mut s TcpSocket) set_option_bool(opt SocketOption, value bool) ! @@ -114,49 +114,49 @@ fn (mut s TcpSocket) bind(addr string) ! fn (mut s UdpSocket) set_option_bool(opt SocketOption, value bool) ! fn (mut s UdpSocket) set_dualstack(on bool) ! enum AddrFamily { - unix = C.AF_UNIX - ip = C.AF_INET - ip6 = C.AF_INET6 - unspec = C.AF_UNSPEC + unix = C.AF_UNIX + ip = C.AF_INET + ip6 = C.AF_INET6 + unspec = C.AF_UNSPEC } AddrFamily are the available address families enum ShutdownDirection { - read - write - read_and_write + read + write + read_and_write } ShutdownDirection is used by `net.shutdown`, for specifying the direction for which the communication will be cut. enum SocketOption { - // TODO: SO_ACCEPT_CONN is not here because windows doesn't support it - // and there is no easy way to define it - broadcast = C.SO_BROADCAST - debug = C.SO_DEBUG - dont_route = C.SO_DONTROUTE - error = C.SO_ERROR - keep_alive = C.SO_KEEPALIVE - linger = C.SO_LINGER - oob_inline = C.SO_OOBINLINE - reuse_addr = C.SO_REUSEADDR - receive_buf_size = C.SO_RCVBUF - receive_low_size = C.SO_RCVLOWAT - receive_timeout = C.SO_RCVTIMEO - send_buf_size = C.SO_SNDBUF - send_low_size = C.SO_SNDLOWAT - send_timeout = C.SO_SNDTIMEO - socket_type = C.SO_TYPE - ipv6_only = C.IPV6_V6ONLY + // TODO: SO_ACCEPT_CONN is not here because windows doesn't support it + // and there is no easy way to define it + broadcast = C.SO_BROADCAST + debug = C.SO_DEBUG + dont_route = C.SO_DONTROUTE + error = C.SO_ERROR + keep_alive = C.SO_KEEPALIVE + linger = C.SO_LINGER + oob_inline = C.SO_OOBINLINE + reuse_addr = C.SO_REUSEADDR + receive_buf_size = C.SO_RCVBUF + receive_low_size = C.SO_RCVLOWAT + receive_timeout = C.SO_RCVTIMEO + send_buf_size = C.SO_SNDBUF + send_low_size = C.SO_SNDLOWAT + send_timeout = C.SO_SNDTIMEO + socket_type = C.SO_TYPE + ipv6_only = C.IPV6_V6ONLY } enum SocketType { - udp = C.SOCK_DGRAM - tcp = C.SOCK_STREAM - seqpacket = C.SOCK_SEQPACKET + udp = C.SOCK_DGRAM + tcp = C.SOCK_STREAM + seqpacket = C.SOCK_SEQPACKET } SocketType are the available sockets struct Addr { pub: - len u8 - f u8 - addr AddrData + len u8 + f u8 + addr AddrData } fn (a Addr) family() AddrFamily family returns the family/kind of the given address `a` @@ -168,72 +168,72 @@ fn (a Addr) str() string str returns a string representation of the address `a` struct C.addrinfo { mut: - ai_family int - ai_socktype int - ai_flags int - ai_protocol int - ai_addrlen int - ai_addr voidptr - ai_canonname voidptr - ai_next voidptr + ai_family int + ai_socktype int + ai_flags int + ai_protocol int + ai_addrlen int + ai_addr voidptr + ai_canonname voidptr + ai_next voidptr } struct C.fd_set {} struct C.sockaddr_in { mut: - sin_len u8 - sin_family u8 - sin_port u16 - sin_addr u32 - sin_zero [8]char + sin_len u8 + sin_family u8 + sin_port u16 + sin_addr u32 + sin_zero [8]char } struct C.sockaddr_in6 { mut: - // 1 + 1 + 2 + 4 + 16 + 4 = 28; - sin6_len u8 // 1 - sin6_family u8 // 1 - sin6_port u16 // 2 - sin6_flowinfo u32 // 4 - sin6_addr [16]u8 // 16 - sin6_scope_id u32 // 4 + // 1 + 1 + 2 + 4 + 16 + 4 = 28; + sin6_len u8 // 1 + sin6_family u8 // 1 + sin6_port u16 // 2 + sin6_flowinfo u32 // 4 + sin6_addr [16]u8 // 16 + sin6_scope_id u32 // 4 } struct C.sockaddr_un { mut: - sun_len u8 - sun_family u8 - sun_path [max_unix_path]char + sun_len u8 + sun_family u8 + sun_path [max_unix_path]char } struct Ip { - port u16 - addr [4]u8 - // Pad to size so that socket functions - // dont complain to us (see in.h and bind()) - // TODO(emily): I would really like to use - // some constant calculations here - // so that this doesnt have to be hardcoded - sin_pad [8]u8 + port u16 + addr [4]u8 + // Pad to size so that socket functions + // dont complain to us (see in.h and bind()) + // TODO(emily): I would really like to use + // some constant calculations here + // so that this doesnt have to be hardcoded + sin_pad [8]u8 } fn (a Ip) str() string str returns a string representation of `a` struct Ip6 { - port u16 - flow_info u32 - addr [16]u8 - scope_id u32 + port u16 + flow_info u32 + addr [16]u8 + scope_id u32 } fn (a Ip6) str() string str returns a string representation of `a` struct ListenOptions { pub: - dualstack bool = true - backlog int = 128 + dualstack bool = true + backlog int = 128 } struct ShutdownConfig { pub: - how ShutdownDirection = .read_and_write + how ShutdownDirection = .read_and_write } struct Socket { pub: - handle int + handle int } fn (s &Socket) address() !Addr address gets the address of a socket @@ -243,13 +243,13 @@ fn (t TCPDialer) dial(address string) !Connection dial will try to create a new abstract connection to the given address. It will return an error, if that is not possible. struct TcpConn { pub mut: - sock TcpSocket - handle int - write_deadline time.Time - read_deadline time.Time - read_timeout time.Duration - write_timeout time.Duration - is_blocking bool = true + sock TcpSocket + handle int + write_deadline time.Time + read_deadline time.Time + read_timeout time.Duration + write_timeout time.Duration + is_blocking bool = true } fn (c &TcpConn) addr() !Addr fn (mut c TcpConn) close() ! @@ -265,7 +265,7 @@ fn (c TcpConn) read(mut buf []u8) !int fn (mut c TcpConn) read_deadline() !time.Time fn (mut con TcpConn) read_line() string read_line is a *simple*, *non customizable*, blocking line reader. It will return a line, ending with LF, or just '', on EOF. - + Note: if you want more control over the buffer, please use a buffered IO reader instead: `io.new_buffered_reader({reader: io.make_reader(con)})` fn (mut con TcpConn) read_line_max(max_line_len int) string read_line_max is a *simple*, *non customizable*, blocking line reader. It will return a line, ending with LF, '' on EOF. It stops reading, when the result line length exceeds max_line_len. @@ -278,7 +278,7 @@ fn (mut c TcpConn) set_read_deadline(deadline time.Time) fn (mut c TcpConn) set_read_timeout(t time.Duration) fn (mut c TcpConn) set_sock() ! set_sock initialises the c.sock field. It should be called after `.accept_only()!`. - + Note: just use `.accept()!`. In most cases it is simpler, and calls `.set_sock()!` for you. fn (mut c TcpConn) set_write_deadline(deadline time.Time) fn (mut c TcpConn) set_write_timeout(t time.Duration) @@ -295,19 +295,17 @@ fn (mut c TcpConn) write_string(s string) !int fn (c &TcpConn) write_timeout() time.Duration struct TcpListener { pub mut: - sock TcpSocket - accept_timeout time.Duration - accept_deadline time.Time - is_blocking bool = true + sock TcpSocket + accept_timeout time.Duration + accept_deadline time.Time + is_blocking bool = true } fn (mut l TcpListener) accept() !&TcpConn accept a tcp connection from an external source to the listener `l`. fn (mut l TcpListener) accept_only() !&TcpConn accept_only accepts a tcp connection from an external source to the listener `l`. Unlike `accept`, `accept_only` *will not call* `.set_sock()!` on the result, and is thus faster. - - - - Note: you *need* to call `.set_sock()!` manually, before using theconnection after calling `.accept_only()!`, but that does not have to happen in the same thread that called `.accept_only()!`. The intention of this API, is to have a more efficient way to accept connections, that are later processed by a thread pool, while the main thread remains active, so that it can accept other connections. See also vlib/vweb/vweb.v . + + Note: you *need* to call `.set_sock()!` manually, before using theconnection after calling `.accept_only()!`, but that does not have to happen in the same thread that called `.accept_only()!`. The intention of this API, is to have a more efficient way to accept connections, that are later processed by a thread pool, while the main thread remains active, so that it can accept other connections. See also vlib/veb/veb.v . If you do not need that, just call `.accept()!` instead, which will call `.set_sock()!` for you. fn (c &TcpListener) accept_deadline() !time.Time @@ -319,12 +317,12 @@ fn (mut c TcpListener) close() ! fn (c &TcpListener) addr() !Addr struct UdpConn { pub mut: - sock UdpSocket + sock UdpSocket mut: - write_deadline time.Time - read_deadline time.Time - read_timeout time.Duration - write_timeout time.Duration + write_deadline time.Time + read_deadline time.Time + read_timeout time.Duration + write_timeout time.Duration } fn (mut c UdpConn) write_ptr(b &u8, len int) !int sock := UdpSocket{ handle: sbase.handle l: local r: resolve_wrapper(raddr) } } @@ -350,5 +348,5 @@ fn (mut c UdpConn) wait_for_write() ! fn (c &UdpConn) str() string fn (mut c UdpConn) close() ! struct Unix { - path [max_unix_path]char + path [max_unix_path]char } diff --git a/lib/dav/webdav/README.md b/lib/dav/webdav/README.md index 40d721e8..ea4f07f9 100644 --- a/lib/dav/webdav/README.md +++ b/lib/dav/webdav/README.md @@ -1,6 +1,6 @@ # **WebDAV Server in V** -This project implements a WebDAV server using the `vweb` framework and modules from `crystallib`. The server supports essential WebDAV file operations such as reading, writing, copying, moving, and deleting files and directories. It also includes **authentication** and **request logging** for better control and debugging. +This project implements a WebDAV server using the `veb` framework and modules from `crystallib`. The server supports essential WebDAV file operations such as reading, writing, copying, moving, and deleting files and directories. It also includes **authentication** and **request logging** for better control and debugging. --- @@ -47,6 +47,7 @@ sudo mount -t davfs ``` For example: + ```bash sudo mount -t davfs http://localhost:8080 /mnt/webdav ``` @@ -82,6 +83,7 @@ Authorization: Basic **Example**: For the credentials `admin:admin`, the header would look like this: + ```http Authorization: Basic YWRtaW46YWRtaW4= ``` @@ -103,25 +105,32 @@ You can configure the WebDAV server using the following parameters when calling ## **Example Workflow** 1. Start the server: + ```bash v run webdav_server.v ``` 2. Mount the server using `davfs`: + ```bash sudo mount -t davfs http://localhost:8080 /mnt/webdav ``` 3. Perform operations: - Create a new file: + ```bash echo "Hello WebDAV!" > /mnt/webdav/hello.txt ``` + - List files: + ```bash ls /mnt/webdav ``` + - Delete a file: + ```bash rm /mnt/webdav/hello.txt ``` @@ -150,7 +159,6 @@ You can configure the WebDAV server using the following parameters when calling - Integration with persistent databases for user credentials. - TLS/SSL support for secure connections. - # WebDAV Property Model This file implements the WebDAV property model as defined in [RFC 4918](https://tools.ietf.org/html/rfc4918). It provides a set of property types that represent various WebDAV properties used in PROPFIND and PROPPATCH operations. @@ -167,12 +175,13 @@ The `model_property.v` file defines: ```v pub interface Property { - xml() string - xml_name() string + xml() string + xml_name() string } ``` All WebDAV properties must implement: + - `xml()`: Returns the full XML representation of the property with its value - `xml_name()`: Returns just the XML tag name of the property (used in property requests) @@ -200,7 +209,6 @@ The file implements the following WebDAV property types: These property types are used when responding to WebDAV PROPFIND requests to describe resources in the WebDAV server. - # WebDAV Locker This file implements a locking mechanism for resources in a WebDAV context. It provides functionality to manage locks on resources, ensuring that they are not modified by multiple clients simultaneously. @@ -218,7 +226,7 @@ The `locker.v` file defines: ```v struct Locker { mut: - locks map[string]Lock + locks map[string]Lock } ``` @@ -229,8 +237,8 @@ mut: ```v pub struct LockResult { pub: - token string // The lock token - is_new_lock bool // Whether this is a new lock or an existing one + token string // The lock token + is_new_lock bool // Whether this is a new lock or an existing one } ``` diff --git a/lib/schemas/openrpc/playground.v b/lib/schemas/openrpc/playground.v index bae8b075..0f3bb407 100644 --- a/lib/schemas/openrpc/playground.v +++ b/lib/schemas/openrpc/playground.v @@ -4,12 +4,13 @@ import x.json2 as json // import freeflowuniverse.herolib.develop.gittools import freeflowuniverse.herolib.core.pathlib import freeflowuniverse.herolib.core.texttools -import vweb +import veb import os pub struct Playground { - vweb.Context - build pathlib.Path @[vweb_global] + veb.Context + veb.StaticHandler + build pathlib.Path @[veb_global] } @[params] @@ -50,11 +51,11 @@ pub fn new_playground(config PlaygroundConfig) !&Playground { build: build_dir } pg.serve_examples(config.specs) or { return error('failed to serve examples:\n${err}') } - pg.mount_static_folder_at('${build_dir.path}/static', '/static') + pg.mount_static_folder_at('${build_dir.path}/static', '/static')! mut env_file := pathlib.get_file(path: '${build_dir.path}/env.js')! env_file.write(encode_env(config.specs)!)! - pg.serve_static('/env.js', env_file.path) + pg.serve_static('/env.js', env_file.path)! return &pg } @@ -86,11 +87,11 @@ fn (mut pg Playground) serve_examples(specs_ []pathlib.Path) ! { return error('Failed to decode OpenRPC Spec ${spec}:\n${err}') } name := texttools.name_fix(o.info.title) - pg.serve_static('/specs/${name}.json', spec.path) + pg.serve_static('/specs/${name}.json', spec.path)! } } -pub fn (mut pg Playground) index() vweb.Result { +pub fn (mut pg Playground) index() veb.Result { mut index := pathlib.get_file(path: '${pg.build.path}/index.html') or { panic(err) } return pg.html(index.read() or { panic(err) }) } diff --git a/lib/security/authentication/client.v b/lib/security/authentication/client.v index cce8ef9f..49eafa09 100644 --- a/lib/security/authentication/client.v +++ b/lib/security/authentication/client.v @@ -4,7 +4,7 @@ import net.http import time import json -// session controller that be be added to vweb projects +// session controller that be be added to veb projects pub struct EmailClient { url string @[required] } diff --git a/lib/security/authentication/controller.v b/lib/security/authentication/controller.v index 9f24d7e5..7ce677ea 100644 --- a/lib/security/authentication/controller.v +++ b/lib/security/authentication/controller.v @@ -1,6 +1,6 @@ module authentication -import vweb +import veb import time import json import log @@ -8,13 +8,13 @@ import freeflowuniverse.herolib.ui.console const agent = 'Email Authentication Controller' -// email authentication controller that be be added to vweb projects +// email authentication controller that be be added to veb projects @[heap] pub struct Controller { - vweb.Context - callback string @[vweb_global] + veb.Context + callback string @[veb_global] mut: - authenticator Authenticator @[vweb_global] + authenticator Authenticator @[veb_global] } @[params] @@ -32,7 +32,7 @@ pub fn new_controller(params ControllerParams) Controller { // route responsible for verifying email, email form should be posted here @[POST] -pub fn (mut app Controller) send_verification_mail() !vweb.Result { +pub fn (mut app Controller) send_verification_mail() !veb.Result { config := json.decode(SendMailConfig, app.req.data)! app.authenticator.send_verification_mail(config) or { panic(err) } return app.ok('') @@ -40,7 +40,7 @@ pub fn (mut app Controller) send_verification_mail() !vweb.Result { // route responsible for verifying email, email form should be posted here @[POST] -pub fn (mut app Controller) is_verified() vweb.Result { +pub fn (mut app Controller) is_verified() veb.Result { address := app.req.data // checks if email verified every 2 seconds for { @@ -55,7 +55,7 @@ pub fn (mut app Controller) is_verified() vweb.Result { // route responsible for verifying email, email form should be posted here @[POST] -pub fn (mut app Controller) email_authentication() vweb.Result { +pub fn (mut app Controller) email_authentication() veb.Result { config_ := json.decode(SendMailConfig, app.req.data) or { app.set_status(422, 'Request payload does not follow anticipated formatting.') return app.text('Request payload does not follow anticipated formatting.') @@ -84,7 +84,7 @@ pub fn (mut app Controller) email_authentication() vweb.Result { // route responsible for verifying email, email form should be posted here @[POST] -pub fn (mut app Controller) verify() vweb.Result { +pub fn (mut app Controller) verify() veb.Result { config_ := json.decode(SendMailConfig, app.req.data) or { app.set_status(422, 'Request payload does not follow anticipated formatting.') return app.text('Request payload does not follow anticipated formatting.') @@ -126,7 +126,7 @@ pub: } @[POST] -pub fn (mut app Controller) authenticate() !vweb.Result { +pub fn (mut app Controller) authenticate() !veb.Result { attempt := json.decode(AuthAttempt, app.req.data)! app.authenticator.authenticate(attempt.address, attempt.cypher) or { app.set_status(401, err.msg()) @@ -136,7 +136,7 @@ pub fn (mut app Controller) authenticate() !vweb.Result { } @['/authentication_link/:address/:cypher'] -pub fn (mut app Controller) authentication_link(address string, cypher string) !vweb.Result { +pub fn (mut app Controller) authentication_link(address string, cypher string) !veb.Result { app.authenticator.authenticate(address, cypher) or { app.set_status(401, err.msg()) return app.text('Failed to authenticate') diff --git a/lib/security/jwt/jwt.v b/lib/security/jwt/jwt.v index 126209dd..249f3199 100644 --- a/lib/security/jwt/jwt.v +++ b/lib/security/jwt/jwt.v @@ -10,7 +10,7 @@ import crypto.rand import os // JWT code in this page is from -// https://github.com/vlang/v/blob/master/examples/vweb_orm_jwt/src/auth_services.v +// https://github.com/vlang/v/blob/master/examples/veb_orm_jwt/src/auth_services.v // credit to https://github.com/enghitalo pub struct JsonWebToken { diff --git a/libarchive/baobab/generator/templates/cli.v.template b/libarchive/baobab/generator/templates/cli.v.template index 2d601bb4..d4762d90 100644 --- a/libarchive/baobab/generator/templates/cli.v.template +++ b/libarchive/baobab/generator/templates/cli.v.template @@ -2,7 +2,7 @@ module @{name} import os import cli { Command } -import vweb +import veb import freeflowuniverse.herolib.schemas.openrpc import freeflowuniverse.herolib.core.pathlib @@ -54,7 +54,7 @@ fn playground(cmd Command) ! { dest: pathlib.get_dir(path: playground_path)! specs: [pathlib.get_file(path:openrpc_path)!] )! - vweb.run(pg, 8080) + veb.run(pg, 8080) } diff --git a/libarchive/baobab/generator/templates/playground.v.template b/libarchive/baobab/generator/templates/playground.v.template index 61811880..871a1736 100644 --- a/libarchive/baobab/generator/templates/playground.v.template +++ b/libarchive/baobab/generator/templates/playground.v.template @@ -1,7 +1,7 @@ #!/usr/bin/env -S v -n -cg -w -enable-globals run import freeflowuniverse.herolib.baobab.stages.accountant -import vweb +import veb import freeflowuniverse.herolib.schemas.openrpc import os import freeflowuniverse.herolib.core.pathlib @@ -13,4 +13,4 @@ pg := openrpc.new_playground( dest: pathlib.get_dir(path: playground_path)! specs: [pathlib.get_file(path:openrpc_path)!] )! -vweb.run(pg, 8080) \ No newline at end of file +veb.run(pg, 8080) \ No newline at end of file