1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

docs: improve vlib/vweb/README.md (#15146)

This commit is contained in:
Hitalo de Jesus do Rosário Souza 2022-08-20 06:06:24 -03:00 committed by GitHub
parent a1de8f5f98
commit eafbf335cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 490 additions and 58 deletions

View File

@ -1,78 +1,140 @@
# vweb - the V Web Server # # vweb - the V Web Server
A simple yet powerful web server with built-in routing, parameter handling, A simple yet powerful web server with built-in routing, parameter handling, templating, and other
templating, and other features. features.
The [gitly](https://gitly.org/) site is based on vweb.
## Alpha level software ## **_Some features may not be complete, and have some bugs._**
Some features may not be complete, and there may still be bugs. However, it is ## Features
still a very useful tool. The [gitly](https://gitly.org/) site is based on vweb.
## Features ##
- **Very fast** performance of C on the web. - **Very fast** performance of C on the web.
- **Small binary** hello world website is <100 KB. - **Small binary** hello world website is <100 KB.
- **Easy to deploy** just one binary file that also includes all templates. - **Easy to deploy** just one binary file that also includes all templates. No need to install any
No need to install any dependencies. dependencies.
- **Templates are precompiled** all errors are visible at compilation time, - **Templates are precompiled** all errors are visible at compilation time, not at runtime.
not at runtime.
There is no formal documentation yet - here is a simple ### Examples
[example](https://github.com/vlang/v/tree/master/examples/vweb/vweb_example.v)
There's also the V forum, [vorum](https://github.com/vlang/vorum) There are some examples
that can be explored [here](https://github.com/vlang/v/tree/master/examples/vweb).
`vorum.v` contains all GET and POST actions. And others like:
- [vweb_orm_jwt](https://github.com/vlang/v/tree/master/examples/vweb_orm_jwt) (back-end)
- [vorum](https://github.com/vlang/vorum) (front-end)
- [gitly](https://github.com/vlang/gitly) (full-stack)
**Front-end getting start example**
`src/main.v`
```v ignore ```v ignore
pub fn (app mut App) index() { module main
posts := app.find_all_posts()
$vweb.html() import vweb
import os
struct App {
vweb.Context
} }
// TODO ['/post/:id/:title'] struct Object {
// TODO `fn (app App) post(id int)` title string
pub fn (app App) post() { description string
id := app.get_post_id()
post := app.retrieve_post(id) or {
app.redirect('/')
return
} }
comments := app.find_comments(id)
show_form := true fn main() {
$vweb.html() vweb.run_at(new_app(), vweb.RunParams{
port: 8081
}) or { panic(err) }
} }
fn new_app() &App {
mut app := &App{}
// makes all static files available.
app.mount_static_folder_at(os.resource_abs_path('.'), '/')
return app
}
['/']
pub fn (mut app App) page_home() vweb.Result {
// all this constants can be accessed by src/templates/page/home.html file.
page_title := 'V is the new V'
v_url := 'https://github.com/vlang/v'
list_of_object := [
Object{
title: 'One good title'
description: 'this is the first'
},
Object{
title: 'Other good title'
description: 'more one'
},
]
// $vweb.html() in `<folder>_<name> vweb.Result ()` like this
// render the `<name>.html` in folder `./templates/<folder>`
return $vweb.html()
}
``` ```
`index.html` is an example of the V template language: `$vweb.html()` compiles an HTML template into V during compilation, and embeds the resulting code
into the current action.
```html
@for post in posts
<div class=post>
<a class=topic href="@post.url">@post.title</a>
<img class=comment-img>
<span class=nr-comments>@post.nr_comments</span>
<span class=time>@post.time</span>
</div>
@end
```
`$vweb.html()` compiles an HTML template into V during compilation,
and embeds the resulting code into the current action.
That means that the template automatically has access to that action's entire environment. That means that the template automatically has access to that action's entire environment.
## Deploying vweb apps ## `src/templates/page/home.html`
```html
<html>
<header>
<title>${page_title}</title>
@css 'src/templates/page/home.css'
</header>
<body>
<h1 class="title">Hello, Vs.</h1>
@for var in list_of_object
<div>
<a href="${v_url}">${var.title}</a>
<span>${var.description}</span>
</div>
@end
<div>@include 'component.html'</div>
</body>
</html>
```
`src/templates/page/component.html`
```html
<div>This is a component</div>
```
`src/templates/page/home.css`
```css
h1.title {
font-family: Arial, Helvetica, sans-serif;
color: #3b7bbf;
}
```
V suport some [Template directives](/vlib/v/TEMPLATES.md) like
`@css`, `@js` for static files in \<path\>
`@if`, `@for` for conditional and loop
and
`@include` to include html components.
## Deploying vweb apps
Everything, including HTML templates, is in one binary file. That's all you need to deploy. Everything, including HTML templates, is in one binary file. That's all you need to deploy.
## Getting Started ## ## Getting Started
To start with vweb, you have to import the module `vweb`. After the import, To start with vweb, you have to import the module `vweb` and define a struct to hold vweb.Context
define a struct to hold vweb.Context (and any other variables your program will (and any other variables your program will need).
need). The web server can be started by calling `vweb.run(&App{}, port)` or `vweb.run(&App{}, RunParams)`
The web server can be started by calling `vweb.run(&App{}, port)`.
**Example:** **Example:**
@ -85,10 +147,16 @@ struct App {
fn main() { fn main() {
vweb.run(&App{}, 8080) vweb.run(&App{}, 8080)
// // or
// vweb.run_at(new_app(), vweb.RunParams{
// host: 'localhost'
// port: 8099
// family: .ip
// }) or { panic(err) }
} }
``` ```
### Defining endpoints ### ### Defining endpoints
To add endpoints to your web server, you have to extend the `App` struct. To add endpoints to your web server, you have to extend the `App` struct.
For routing you can either use auto-mapping of function names or specify the path as an attribute. For routing you can either use auto-mapping of function names or specify the path as an attribute.
@ -109,7 +177,11 @@ fn (mut app App) world() vweb.Result {
} }
``` ```
To create an HTTP POST endpoint, you simply add a `[post]` attribute before the function definition. #### - HTTP verbs
To use any HTTP verbs (or methods, as they are properly called),
such as `[post]`, `[get]`, `[put]`, `[patch]` or `[delete]`
you can simply add the attribute before the function definition.
**Example:** **Example:**
@ -118,16 +190,26 @@ To create an HTTP POST endpoint, you simply add a `[post]` attribute before the
fn (mut app App) world() vweb.Result { fn (mut app App) world() vweb.Result {
return app.text('World') return app.text('World')
} }
['/product/create'; post]
fn (mut app App) create_product() vweb.Result {
return app.text('product')
}
``` ```
To pass a parameter to an endpoint, you simply define it inside #### - Parameters
an attribute, e. g. `['/hello/:user]`.
Parameters are passed direcly in endpoint route using colon sign `:` and received using the same
name at function
To pass a parameter to an endpoint, you simply define it inside an attribute, e. g.
`['/hello/:user]`.
After it is defined in the attribute, you have to add it as a function parameter. After it is defined in the attribute, you have to add it as a function parameter.
**Example:** **Example:**
```v ignore ```v ignore
['/hello/:user'] vvvv
['/hello/:user'] vvvv
fn (mut app App) hello_user(user string) vweb.Result { fn (mut app App) hello_user(user string) vweb.Result {
return app.text('Hello $user') return app.text('Hello $user')
} }
@ -139,3 +221,343 @@ If you want to read the request body, you can do that by calling `app.req.data`.
To read the request headers, you just call `app.req.header` and access the To read the request headers, you just call `app.req.header` and access the
header you want, e.g. `app.req.header.get(.content_type)`. See `struct Header` header you want, e.g. `app.req.header.get(.content_type)`. See `struct Header`
for all available methods (`v doc net.http Header`). for all available methods (`v doc net.http Header`).
### Middleware
V haven't a well defined middleware.
For now, you can use `before_request()`. This method called before every request.
Probably you can use it for check user session cookie or add header
**Example:**
```v ignore
pub fn (mut app App) before_request() {
app.user_id = app.get_cookie('id') or { '0' }
}
```
### Redirect
Used when you want be redirected to an url
**Examples:**
```v ignore
pub fn (mut app App) before_request() {
app.user_id = app.get_cookie('id') or { app.redirect('/') }
}
```
```v ignore
['/articles'; get]
pub fn (mut app App) articles() vweb.Result {
if !app.token {
app.redirect('/login')
}
return app.text("patatoes")
}
```
### Responses
#### - set_status
Sets the response status
**Example:**
```v ignore
['/user/get_all'; get]
pub fn (mut app App) controller_get_all_user() vweb.Result {
token := app.get_header('token')
if !token {
app.set_status(401, '')
return app.text('Not valid token')
}
response := app.service_get_all_user() or {
app.set_status(400, '')
return app.text('$err')
}
return app.json(response)
}
```
#### - html
Response HTTP_OK with payload with content-type `text/html`
**Example:**
```v ignore
pub fn (mut app App) html_page() vweb.Result {
return app.html('<h1>ok</h1>')
}
```
#### - text
Response HTTP_OK with payload with content-type `text/plain`
**Example:**
```v ignore
pub fn (mut app App) simple() vweb.Result {
return app.text('A simple result')
}
```
#### - json
Response HTTP_OK with payload with content-type `application/json`
**Examples:**
```v ignore
['/articles'; get]
pub fn (mut app App) articles() vweb.Result {
articles := app.find_all_articles()
json_result := json.encode(articles)
return app.json(json_result)
}
```
```v ignore
['/user/create'; post]
pub fn (mut app App) controller_create_user() vweb.Result {
body := json.decode(User, app.req.data) or {
app.set_status(400, '')
return app.text('Failed to decode json, error: $err')
}
response := app.service_add_user(body.username, body.password) or {
app.set_status(400, '')
return app.text('error: $err')
}
return app.json(response)
}
```
#### - json_pretty
Response HTTP_OK with a pretty-printed JSON result
**Example:**
```v ignore
fn (mut app App) time_json_pretty() {
app.json_pretty({
'time': time.now().format()
})
}
```
#### - file
Response HTTP_OK with file as payload
#### - ok
Response HTTP_OK with payload
**Example:**
```v ignore
['/form_echo'; post]
pub fn (mut app App) form_echo() vweb.Result {
app.set_content_type(app.req.header.get(.content_type) or { '' })
return app.ok(app.form['foo'])
}
```
#### - server_error
Response a server error
**Example:**
```v ignore
fn (mut app App) sse() vweb.Result {
return app.server_error(501)
}
```
#### - not_found
Response HTTP_NOT_FOUND with payload
**Example:**
```v ignore
['/:user/:repo/settings']
pub fn (mut app App) user_repo_settings(username string, repository string) vweb.Result {
if username !in known_users {
return app.not_found()
}
return app.html('username: $username | repository: $repository')
}
```
### Requests
#### - get_header
Returns the header data from the key
**Example:**
```v ignore
['/user/get_all'; get]
pub fn (mut app App) controller_get_all_user() vweb.Result {
token := app.get_header('token')
return app.text(token)
}
```
#### - get_cookie
Sets a cookie
**Example:**
```v ignore
pub fn (mut app App) before_request() {
app.user_id = app.get_cookie('id') or { '0' }
}
```
#### - add_header
Adds an header to the response with key and val
**Example:**
```v ignore
['/upload'; post]
pub fn (mut app App) upload() vweb.Result {
fdata := app.files['upfile']
data_rows := fdata[0].data.split('\n')
mut output_data := ''
for elem in data_rows {
delim_row := elem.split('\t')
output_data += '${delim_row[0]}\t${delim_row[1]}\t'
output_data += '${delim_row[0].int() + delim_row[1].int()}\n'
}
output_data = output_data.all_before_last('\n')
app.add_header('Content-Disposition', 'attachment; filename=results.txt')
app.send_response_to_client('application/octet-stream', output_data)
return $vweb.html()
}
```
#### - set_cookie
Sets a cookie
**Example:**
```v ignore
pub fn (mut app App) cookie() vweb.Result {
app.set_cookie(name: 'cookie', value: 'test')
return app.text('Response Headers\n$app.header')
}
```
#### - set_cookie_with_expire_date
Sets a cookie with a `expire_data`
**Example:**
```v ignore
pub fn (mut app App) cookie() vweb.Result {
key := 'cookie'
value := 'test'
duration := time.Duration(2 * time.minute ) // add 2 minutes
expire_date := time.now().add(duration)
app.set_cookie_with_expire_date(key, value, expire_date)
return app.text('Response Headers\n$app.header')
}
```
#### - set_content_type
Sets the response content type
**Example:**
```v ignore
['/form_echo'; post]
pub fn (mut app App) form_echo() vweb.Result {
app.set_content_type(app.req.header.get(.content_type) or { '' })
return app.ok(app.form['foo'])
}
```
### Template
#### -handle_static
handle_static is used to mark a folder (relative to the current working folder) as one that
contains only static resources (css files, images etc).
If `root` is set the mount path for the dir will be in '/'
**Example:**
```v ignore
fn main() {
mut app := &App{}
app.serve_static('/favicon.ico', 'favicon.ico')
// Automatically make available known static mime types found in given directory.
os.chdir(os.dir(os.executable()))?
app.handle_static('assets', true)
vweb.run(app, port)
}
```
#### -mount_static_folder_at
makes all static files in `directory_path` and inside it, available at http://server/mount_path.
For example: suppose you have called .mount_static_folder_at('/var/share/myassets', '/assets'),
and you have a file /var/share/myassets/main.css .
=> That file will be available at URL: http://server/assets/main.css .
#### -serve_static
Serves a file static.
`url` is the access path on the site, `file_path` is the real path to the file, `mime_type` is the
file type
**Example:**
```v ignore
fn main() {
mut app := &App{}
app.serve_static('/favicon.ico', 'favicon.ico')
app.mount_static_folder_at(os.resource_abs_path('.'), '/')
vweb.run(app, 8081)
}
```
### Others
#### -ip
Returns the ip address from the current user
**Example:**
```v ignore
pub fn (mut app App) ip() vweb.Result {
ip := app.ip()
return app.text('ip: $ip')
}
```
#### -error
Set a string to the form error
**Example:**
```v ignore
pub fn (mut app App) error() vweb.Result {
app.error('here as an error')
println(app.form_error) //'vweb error: here as an error'
}
```

View File

@ -193,6 +193,7 @@ pub fn (ctx Context) init_server() {
// Probably you can use it for check user session cookie or add header. // Probably you can use it for check user session cookie or add header.
pub fn (ctx Context) before_request() {} pub fn (ctx Context) before_request() {}
// TODO - test
// vweb intern function // vweb intern function
[manualfree] [manualfree]
pub fn (mut ctx Context) send_response_to_client(mimetype string, res string) bool { pub fn (mut ctx Context) send_response_to_client(mimetype string, res string) bool {
@ -243,6 +244,7 @@ pub fn (mut ctx Context) json_pretty<T>(j T) Result {
return Result{} return Result{}
} }
// TODO - test
// Response HTTP_OK with file as payload // Response HTTP_OK with file as payload
pub fn (mut ctx Context) file(f_path string) Result { pub fn (mut ctx Context) file(f_path string) Result {
ext := os.file_ext(f_path) ext := os.file_ext(f_path)
@ -267,6 +269,7 @@ pub fn (mut ctx Context) ok(s string) Result {
return Result{} return Result{}
} }
// TODO - test
// Response a server error // Response a server error
pub fn (mut ctx Context) server_error(ecode int) Result { pub fn (mut ctx Context) server_error(ecode int) Result {
$if debug { $if debug {
@ -302,6 +305,7 @@ pub fn (mut ctx Context) not_found() Result {
return Result{} return Result{}
} }
// TODO - test
// Sets a cookie // Sets a cookie
pub fn (mut ctx Context) set_cookie(cookie http.Cookie) { pub fn (mut ctx Context) set_cookie(cookie http.Cookie) {
mut cookie_data := []string{} mut cookie_data := []string{}
@ -320,6 +324,7 @@ pub fn (mut ctx Context) set_content_type(typ string) {
ctx.content_type = typ ctx.content_type = typ
} }
// TODO - test
// Sets a cookie with a `expire_data` // Sets a cookie with a `expire_data`
pub fn (mut ctx Context) set_cookie_with_expire_date(key string, val string, expire_date time.Time) { pub fn (mut ctx Context) set_cookie_with_expire_date(key string, val string, expire_date time.Time) {
ctx.add_header('Set-Cookie', '$key=$val; Secure; HttpOnly; expires=$expire_date.utc_string()') ctx.add_header('Set-Cookie', '$key=$val; Secure; HttpOnly; expires=$expire_date.utc_string()')
@ -345,6 +350,7 @@ pub fn (ctx &Context) get_cookie(key string) ?string { // TODO refactor
return error('Cookie not found') return error('Cookie not found')
} }
// TODO - test
// Sets the response status // Sets the response status
pub fn (mut ctx Context) set_status(code int, desc string) { pub fn (mut ctx Context) set_status(code int, desc string) {
if code < 100 || code > 599 { if code < 100 || code > 599 {
@ -354,11 +360,13 @@ pub fn (mut ctx Context) set_status(code int, desc string) {
} }
} }
// TODO - test
// Adds an header to the response with key and val // Adds an header to the response with key and val
pub fn (mut ctx Context) add_header(key string, val string) { pub fn (mut ctx Context) add_header(key string, val string) {
ctx.header.add_custom(key, val) or {} ctx.header.add_custom(key, val) or {}
} }
// TODO - test
// Returns the header data from the key // Returns the header data from the key
pub fn (ctx &Context) get_header(key string) string { pub fn (ctx &Context) get_header(key string) string {
return ctx.req.header.get_custom(key) or { '' } return ctx.req.header.get_custom(key) or { '' }
@ -381,7 +389,7 @@ pub struct RunParams {
} }
// run_at - start a new VWeb server, listening only on a specific address `host`, at the specified `port` // run_at - start a new VWeb server, listening only on a specific address `host`, at the specified `port`
// Example: vweb.run_at(app, 'localhost', 8099) // Example: vweb.run_at(new_app(), vweb.RunParams{ host: 'localhost' port: 8099 family: .ip }) or { panic(err) }
[manualfree] [manualfree]
pub fn run_at<T>(global_app &T, params RunParams) ? { pub fn run_at<T>(global_app &T, params RunParams) ? {
if params.port <= 0 || params.port > 65535 { if params.port <= 0 || params.port > 65535 {
@ -653,6 +661,7 @@ pub fn (mut ctx Context) handle_static(directory_path string, root bool) bool {
return true return true
} }
// TODO - test
// mount_static_folder_at - makes all static files in `directory_path` and inside it, available at http://server/mount_path // mount_static_folder_at - makes all static files in `directory_path` and inside it, available at http://server/mount_path
// For example: suppose you have called .mount_static_folder_at('/var/share/myassets', '/assets'), // For example: suppose you have called .mount_static_folder_at('/var/share/myassets', '/assets'),
// and you have a file /var/share/myassets/main.css . // and you have a file /var/share/myassets/main.css .
@ -668,6 +677,7 @@ pub fn (mut ctx Context) mount_static_folder_at(directory_path string, mount_pat
return true return true
} }
// TODO - test
// Serves a file static // Serves a file static
// `url` is the access path on the site, `file_path` is the real path to the file, `mime_type` is the file type // `url` is the access path on the site, `file_path` is the real path to the file, `mime_type` is the file type
pub fn (mut ctx Context) serve_static(url string, file_path string) { pub fn (mut ctx Context) serve_static(url string, file_path string) {