veb: deprecate vweb (part 2); move all vweb examples to veb
@ -289,7 +289,7 @@ V comes with a version of mbedtls, which should work on all systems. If you find
|
|||||||
use OpenSSL instead, you will need to make sure that it is installed on your system, then
|
use OpenSSL instead, you will need to make sure that it is installed on your system, then
|
||||||
use the `-d use_openssl` switch when you compile.
|
use the `-d use_openssl` switch when you compile.
|
||||||
|
|
||||||
Note: Mbed-TLS is smaller and easier to install on windows too (V comes with it), but if you
|
Note: Mbed-TLS is smaller and easier to install on windows too (V comes with it), but if you
|
||||||
write programs, that do lots of http requests to HTTPS/SSL servers, in most cases, it is *best*
|
write programs, that do lots of http requests to HTTPS/SSL servers, in most cases, it is *best*
|
||||||
to compile with `-d use_openssl`, and do so on a system, where you do have OpenSSL installed
|
to compile with `-d use_openssl`, and do so on a system, where you do have OpenSSL installed
|
||||||
(see below). Mbed-TLS is slower, and can have more issues, especially when you are doing parallel
|
(see below). Mbed-TLS is slower, and can have more issues, especially when you are doing parallel
|
||||||
@ -363,7 +363,7 @@ With V's `vab` tool, building V UI and graphical apps for Android can become as
|
|||||||
## Developing web applications
|
## Developing web applications
|
||||||
|
|
||||||
Check out the
|
Check out the
|
||||||
[Building a simple web blog](https://github.com/vlang/v/blob/master/tutorials/building_a_simple_web_blog_with_vweb/README.md)
|
[Building a simple web blog](https://github.com/vlang/v/blob/master/tutorials/building_a_simple_web_blog_with_veb/README.md)
|
||||||
tutorial and Gitly, a light and fast alternative to GitHub/GitLab:
|
tutorial and Gitly, a light and fast alternative to GitHub/GitLab:
|
||||||
|
|
||||||
https://github.com/vlang/gitly
|
https://github.com/vlang/gitly
|
||||||
|
@ -115,7 +115,7 @@ fn run_vsh_script_tcc() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn test_v_tutorials_tcc() {
|
fn test_v_tutorials_tcc() {
|
||||||
exec('v tutorials/building_a_simple_web_blog_with_vweb/code/blog')
|
exec('v tutorials/building_a_simple_web_blog_with_veb/code/blog')
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_fast_tcc() {
|
fn build_fast_tcc() {
|
||||||
@ -228,7 +228,7 @@ fn build_tetris_autofree_gcc() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn build_blog_autofree_gcc() {
|
fn build_blog_autofree_gcc() {
|
||||||
exec('v -autofree -o blog tutorials/building_a_simple_web_blog_with_vweb/code/blog')
|
exec('v -autofree -o blog tutorials/building_a_simple_web_blog_with_veb/code/blog')
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_option_test_autofree_gcc() {
|
fn build_option_test_autofree_gcc() {
|
||||||
|
@ -76,7 +76,7 @@ fn build_tetris_autofree() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn build_blog_autofree() {
|
fn build_blog_autofree() {
|
||||||
exec('v -autofree -o blog tutorials/building_a_simple_web_blog_with_vweb/code/blog')
|
exec('v -autofree -o blog tutorials/building_a_simple_web_blog_with_veb/code/blog')
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_examples_prod() {
|
fn build_examples_prod() {
|
||||||
|
40
examples/veb/file_upload/file_uploading.v
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
module main
|
||||||
|
|
||||||
|
import veb
|
||||||
|
|
||||||
|
const port = 8082
|
||||||
|
|
||||||
|
struct App {
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Context {
|
||||||
|
veb.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
mut app := &App{}
|
||||||
|
veb.run[App, Context](mut app, port)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut app App) index() veb.Result {
|
||||||
|
return $veb.html()
|
||||||
|
}
|
||||||
|
|
||||||
|
@['/upload'; post]
|
||||||
|
pub fn (mut app App) upload() veb.Result {
|
||||||
|
dump(ctx.form)
|
||||||
|
dump(ctx.files)
|
||||||
|
fdata := ctx.files['upfile']
|
||||||
|
mut files := []veb.RawHtml{}
|
||||||
|
for d in fdata {
|
||||||
|
files << d.data.replace_each(['\n', '<br>', '\n\r', '<br>', '\t', ' ', ' ', ' '])
|
||||||
|
}
|
||||||
|
return $veb.html()
|
||||||
|
}
|
||||||
|
|
||||||
|
@['/submit'; post]
|
||||||
|
pub fn (mut app App) submit() veb.Result {
|
||||||
|
dump(ctx.form)
|
||||||
|
form_data := ctx.form.clone()
|
||||||
|
return $veb.html()
|
||||||
|
}
|
Before Width: | Height: | Size: 671 B After Width: | Height: | Size: 671 B |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
44
examples/veb/server_sent_events/server.v
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
module main
|
||||||
|
|
||||||
|
import os
|
||||||
|
import rand
|
||||||
|
import time
|
||||||
|
import veb
|
||||||
|
import veb.sse
|
||||||
|
|
||||||
|
struct App {
|
||||||
|
veb.StaticHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Context {
|
||||||
|
veb.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
mut app := &App{}
|
||||||
|
app.serve_static('/favicon.ico', 'favicon.ico')!
|
||||||
|
app.mount_static_folder_at(os.resource_abs_path('.'), '/')!
|
||||||
|
veb.run[App, Context](mut app, 8081)
|
||||||
|
}
|
||||||
|
|
||||||
|
// XTODO template broken (@)
|
||||||
|
pub fn (mut app App) index() veb.Result {
|
||||||
|
title := 'SSE Example'
|
||||||
|
return $veb.html()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut app App) sse() veb.Result {
|
||||||
|
mut session := sse.start_connection(mut ctx.Context)
|
||||||
|
// Note: you can setup session.write_timeout and session.headers here
|
||||||
|
// session.start() or { return app.server_error(501) }
|
||||||
|
session.send_message(data: 'ok') or { return ctx.server_error_with_status(.not_implemented) }
|
||||||
|
for {
|
||||||
|
data := '{"time": "${time.now().str()}", "random_id": "${rand.ulid()}"}'
|
||||||
|
session.send_message(event: 'ping', data: data) or {
|
||||||
|
return ctx.server_error_with_status(.not_implemented)
|
||||||
|
}
|
||||||
|
println('> sent event: ${data}')
|
||||||
|
time.sleep(1 * time.second)
|
||||||
|
}
|
||||||
|
return ctx.server_error_with_status(.not_implemented)
|
||||||
|
}
|
@ -1,37 +0,0 @@
|
|||||||
module main
|
|
||||||
|
|
||||||
import vweb
|
|
||||||
|
|
||||||
const port = 8082
|
|
||||||
|
|
||||||
struct App {
|
|
||||||
vweb.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
vweb.run(&App{}, port)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (mut app App) index() vweb.Result {
|
|
||||||
return $vweb.html()
|
|
||||||
}
|
|
||||||
|
|
||||||
@['/upload'; post]
|
|
||||||
pub fn (mut app App) upload() vweb.Result {
|
|
||||||
dump(app.form)
|
|
||||||
dump(app.files)
|
|
||||||
fdata := app.files['upfile']
|
|
||||||
mut files := []vweb.RawHtml{}
|
|
||||||
for d in fdata {
|
|
||||||
files << d.data.replace_each(['\n', '<br>', '\n\r', '<br>', '\t', ' ', ' ', ' '])
|
|
||||||
}
|
|
||||||
|
|
||||||
return $vweb.html()
|
|
||||||
}
|
|
||||||
|
|
||||||
@['/submit'; post]
|
|
||||||
pub fn (mut app App) submit() vweb.Result {
|
|
||||||
dump(app.form)
|
|
||||||
form_data := app.form.clone()
|
|
||||||
return $vweb.html()
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
module main
|
|
||||||
|
|
||||||
import os
|
|
||||||
import rand
|
|
||||||
import time
|
|
||||||
import vweb
|
|
||||||
import vweb.sse
|
|
||||||
|
|
||||||
struct App {
|
|
||||||
vweb.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (mut app App) index() vweb.Result {
|
|
||||||
title := 'SSE Example'
|
|
||||||
return $vweb.html()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn (mut app App) sse() vweb.Result {
|
|
||||||
mut session := sse.new_connection(app.conn)
|
|
||||||
// Note: you can setup session.write_timeout and session.headers here
|
|
||||||
session.start() or { return app.server_error(501) }
|
|
||||||
session.send_message(data: 'ok') or { return app.server_error(501) }
|
|
||||||
for {
|
|
||||||
data := '{"time": "${time.now().str()}", "random_id": "${rand.ulid()}"}'
|
|
||||||
session.send_message(event: 'ping', data: data) or { return app.server_error(501) }
|
|
||||||
println('> sent event: ${data}')
|
|
||||||
time.sleep(1 * time.second)
|
|
||||||
}
|
|
||||||
return app.server_error(501)
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
const messageList = document.getElementById('message-list');
|
|
||||||
const protocol = location.protocol === 'https:' ? 'wss' : 'ws';
|
|
||||||
const socket = new WebSocket(`${protocol}://${location.host}/ws`);
|
|
||||||
let i = 0;
|
|
||||||
|
|
||||||
function send(message) {
|
|
||||||
messageList.innerHTML += `<li>> ${message}</li>`;
|
|
||||||
socket.send(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.addEventListener("open", (event) => {
|
|
||||||
console.log('Connected to WS server');
|
|
||||||
send('Hey everyone !');
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.addEventListener("message", (event) => {
|
|
||||||
const { data } = event;
|
|
||||||
messageList.innerHTML += `<li>< ${data}</li>`;
|
|
||||||
setTimeout(() => {
|
|
||||||
send(`Roger ${i++}`);
|
|
||||||
}, 3000);
|
|
||||||
});
|
|
@ -1,11 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang=en>
|
|
||||||
<head>
|
|
||||||
<meta charset=utf-8>
|
|
||||||
<title>vweb websocket example page</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<ol id="message-list"></ol>
|
|
||||||
<script type="text/javascript" src="websocket_client.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,75 +0,0 @@
|
|||||||
module main
|
|
||||||
|
|
||||||
import log
|
|
||||||
import net.http
|
|
||||||
import net.websocket
|
|
||||||
import term
|
|
||||||
import vweb
|
|
||||||
|
|
||||||
const http_port = 8080
|
|
||||||
|
|
||||||
struct App {
|
|
||||||
vweb.Context
|
|
||||||
mut:
|
|
||||||
wss &websocket.Server @[vweb_global]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn slog(message string) {
|
|
||||||
eprintln(term.colorize(term.bright_yellow, message))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clog(message string) {
|
|
||||||
eprintln(term.colorize(term.cyan, message))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn wlog(message string) {
|
|
||||||
eprintln(term.colorize(term.bright_blue, message))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
mut app := new_app() or { panic(err) }
|
|
||||||
vweb.run(app, http_port)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_app() !&App {
|
|
||||||
mut app := &App{
|
|
||||||
wss: new_websocker_server()!
|
|
||||||
}
|
|
||||||
app.handle_static('assets', true)
|
|
||||||
return app
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_websocker_server() !&websocket.Server {
|
|
||||||
mut logger := &log.Log{}
|
|
||||||
logger.set_level(.debug)
|
|
||||||
mut wss := websocket.new_server(.ip, 8080, '', logger: logger)
|
|
||||||
wss.on_connect(fn (mut server_client websocket.ServerClient) !bool {
|
|
||||||
slog('ws.on_connect, server_client.client_key: ${server_client.client_key}')
|
|
||||||
return true
|
|
||||||
})!
|
|
||||||
wss.on_message(fn (mut ws websocket.Client, msg &websocket.Message) ! {
|
|
||||||
slog('s.on_message msg.opcode: ${msg.opcode} | msg.payload: ${msg.payload}')
|
|
||||||
ws.write(msg.payload, msg.opcode) or {
|
|
||||||
eprintln('ws.write err: ${err}')
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
})
|
|
||||||
wss.on_close(fn (mut ws websocket.Client, code int, reason string) ! {
|
|
||||||
slog('s.on_close code: ${code}, reason: ${reason}')
|
|
||||||
})
|
|
||||||
slog('Websocket Server initialized')
|
|
||||||
return wss
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (mut app App) index() vweb.Result {
|
|
||||||
return $vweb.html()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn (mut app App) ws() !vweb.Result {
|
|
||||||
key := app.req.header.get(http.CommonHeader.sec_websocket_key)!
|
|
||||||
app.wss.handle_handshake(mut app.conn, key) or {
|
|
||||||
wlog('handle_handshake error: ${err.msg()}')
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return app.text('')
|
|
||||||
}
|
|
@ -83,7 +83,7 @@ Running a Veb app on http://localhost:8081 ...
|
|||||||
|
|
||||||
Veb helpfully provided a link, open http://localhost:8081/ in your browser:
|
Veb helpfully provided a link, open http://localhost:8081/ in your browser:
|
||||||
|
|
||||||
<img width=662 src="https://github.com/vlang/v/blob/master/tutorials/building_a_simple_web_blog_with_vweb/img/hello.png?raw=true">
|
<img width=662 src="https://github.com/vlang/v/blob/master/tutorials/building_a_simple_web_blog_with_veb/img/hello.png?raw=true">
|
||||||
|
|
||||||
The App struct holds shared application data, while Context handles per-request data and embeds
|
The App struct holds shared application data, while Context handles per-request data and embeds
|
||||||
`veb.Context` for response methods like `.text()`.
|
`veb.Context` for response methods like `.text()`.
|
||||||
@ -109,7 +109,7 @@ fn (mut app App) time() veb.Result {
|
|||||||
|
|
||||||
Custom routes can be defined using attributes like @['/index'].
|
Custom routes can be defined using attributes like @['/index'].
|
||||||
|
|
||||||
<img width=662 src="https://github.com/vlang/v/blob/master/tutorials/building_a_simple_web_blog_with_vweb/img/time.png?raw=true">
|
<img width=662 src="https://github.com/vlang/v/blob/master/tutorials/building_a_simple_web_blog_with_veb/img/time.png?raw=true">
|
||||||
|
|
||||||
> TIP: run the following command to live-reload the server: `v watch run blog.v`
|
> TIP: run the following command to live-reload the server: `v watch run blog.v`
|
||||||
|
|
||||||
@ -143,7 +143,7 @@ pub fn (mut app App) index() veb.Result {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
<img width=662 src="https://github.com/vlang/v/blob/master/tutorials/building_a_simple_web_blog_with_vweb/img/hello_html.png?raw=true">
|
<img width=662 src="https://github.com/vlang/v/blob/master/tutorials/building_a_simple_web_blog_with_veb/img/hello_html.png?raw=true">
|
||||||
|
|
||||||
Good, now we have an actual HTML page.
|
Good, now we have an actual HTML page.
|
||||||
|
|
||||||
@ -278,7 +278,7 @@ Finally, let's update our view:
|
|||||||
v run .
|
v run .
|
||||||
```
|
```
|
||||||
|
|
||||||
<img width=662 src="https://github.com/vlang/v/blob/master/tutorials/building_a_simple_web_blog_with_vweb/img/articles1.png?raw=true">
|
<img width=662 src="https://github.com/vlang/v/blob/master/tutorials/building_a_simple_web_blog_with_veb/img/articles1.png?raw=true">
|
||||||
|
|
||||||
That was very simple, wasn't it?
|
That was very simple, wasn't it?
|
||||||
|
|
||||||
@ -398,7 +398,7 @@ pub fn (mut app App) articles() veb.Result {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
<img width=662 src="https://github.com/vlang/v/blob/master/tutorials/building_a_simple_web_blog_with_vweb/img/articles_json.png?raw=true">
|
<img width=662 src="https://github.com/vlang/v/blob/master/tutorials/building_a_simple_web_blog_with_veb/img/articles_json.png?raw=true">
|
||||||
|
|
||||||
### Persistent data
|
### Persistent data
|
||||||
|
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB |
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 108 KiB |
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
@ -117,6 +117,7 @@ pub const mime_types = {
|
|||||||
'.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
|
'.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
|
||||||
'.rar': 'application/vnd.rar'
|
'.rar': 'application/vnd.rar'
|
||||||
'.rtf': 'application/rtf'
|
'.rtf': 'application/rtf'
|
||||||
|
'.scss': 'text/css'
|
||||||
'.sh': 'application/x-sh'
|
'.sh': 'application/x-sh'
|
||||||
'.svg': 'image/svg+xml'
|
'.svg': 'image/svg+xml'
|
||||||
'.swf': 'application/x-shockwave-flash'
|
'.swf': 'application/x-shockwave-flash'
|
||||||
|
@ -227,6 +227,12 @@ pub fn (mut ctx Context) server_error(msg string) Result {
|
|||||||
return ctx.send_response_to_client('text/plain', msg)
|
return ctx.send_response_to_client('text/plain', msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// send an error with a custom status
|
||||||
|
pub fn (mut ctx Context) server_error_with_status(s http.Status) Result {
|
||||||
|
ctx.res.set_status(s)
|
||||||
|
return ctx.send_response_to_client('text/plain', 'Server error')
|
||||||
|
}
|
||||||
|
|
||||||
// send a 204 No Content response without body and content-type
|
// send a 204 No Content response without body and content-type
|
||||||
pub fn (mut ctx Context) no_content() Result {
|
pub fn (mut ctx Context) no_content() Result {
|
||||||
ctx.res.set_status(.no_content)
|
ctx.res.set_status(.no_content)
|
||||||
|