mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
context, vweb: add ability to set and get values on vweb.Context (#18564)
This commit is contained in:
parent
7a9c885b31
commit
21d9730cde
@ -65,12 +65,41 @@ pub interface Any {}
|
|||||||
pub interface Context {
|
pub interface Context {
|
||||||
deadline() ?time.Time
|
deadline() ?time.Time
|
||||||
value(key Key) ?Any
|
value(key Key) ?Any
|
||||||
str() string
|
|
||||||
mut:
|
mut:
|
||||||
done() chan int
|
done() chan int
|
||||||
err() IError
|
err() IError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// str returns the `str` method of the corresponding Context struct
|
||||||
|
pub fn (ctx &Context) str() string {
|
||||||
|
// since `Context` is an interface we have to manually match every possible
|
||||||
|
// type that implements `Context` if we want to use a `Context` as a field in a struct
|
||||||
|
// since the `Context` interface has to implement its own `str` method.
|
||||||
|
match ctx {
|
||||||
|
BackgroundContext {
|
||||||
|
return ctx.str()
|
||||||
|
}
|
||||||
|
EmptyContext {
|
||||||
|
return ctx.str()
|
||||||
|
}
|
||||||
|
TodoContext {
|
||||||
|
return ctx.str()
|
||||||
|
}
|
||||||
|
CancelContext {
|
||||||
|
return ctx.str()
|
||||||
|
}
|
||||||
|
TimerContext {
|
||||||
|
return ctx.str()
|
||||||
|
}
|
||||||
|
ValueContext {
|
||||||
|
return ctx.str()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return context_name(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn context_name(ctx Context) string {
|
fn context_name(ctx Context) string {
|
||||||
return typeof(ctx)
|
return typeof(ctx)
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ module context
|
|||||||
|
|
||||||
fn test_background() {
|
fn test_background() {
|
||||||
ctx := background()
|
ctx := background()
|
||||||
assert 'context.Background' == ctx.str()
|
assert '&context.Background' == ctx.str()
|
||||||
if _ := ctx.value('') {
|
if _ := ctx.value('') {
|
||||||
panic('This should never happen')
|
panic('This should never happen')
|
||||||
}
|
}
|
||||||
@ -10,7 +10,7 @@ fn test_background() {
|
|||||||
|
|
||||||
fn test_todo() {
|
fn test_todo() {
|
||||||
ctx := todo()
|
ctx := todo()
|
||||||
assert 'context.TODO' == ctx.str()
|
assert '&context.TODO' == ctx.str()
|
||||||
if _ := ctx.value('') {
|
if _ := ctx.value('') {
|
||||||
panic('This should never happen')
|
panic('This should never happen')
|
||||||
}
|
}
|
||||||
|
@ -377,6 +377,81 @@ If any function of step 2 or 3 returns `false` the middleware functions that wou
|
|||||||
come after it are not executed and the app handler will also not be executed. You
|
come after it are not executed and the app handler will also not be executed. You
|
||||||
can think of it as a chain.
|
can think of it as a chain.
|
||||||
|
|
||||||
|
### Context values
|
||||||
|
|
||||||
|
You can store a value pair in vweb's context. It is especially usefull for passing variables
|
||||||
|
from a middleware function to the route handler.
|
||||||
|
|
||||||
|
**Example**:
|
||||||
|
```v oksyntax
|
||||||
|
module main
|
||||||
|
|
||||||
|
import vweb
|
||||||
|
|
||||||
|
struct App {
|
||||||
|
vweb.Context
|
||||||
|
middlewares map[string][]vweb.Middleware
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut app App) index() vweb.Result {
|
||||||
|
// get the user or return HTTP 401
|
||||||
|
user := app.get_value[User]('user') or {
|
||||||
|
app.set_status(401, '')
|
||||||
|
return app.text('HTTP 401: Unauthorized')
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.text('welcome ${user.name}')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
vweb.run(&App{
|
||||||
|
middlewares: {
|
||||||
|
'/': [get_session]
|
||||||
|
}
|
||||||
|
}, 8080)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct User {
|
||||||
|
session_id string
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_session(mut ctx vweb.Context) bool {
|
||||||
|
// impelement your own logic to get the user
|
||||||
|
user := User{
|
||||||
|
session_id: '123456'
|
||||||
|
name: 'Vweb'
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the user
|
||||||
|
ctx.set_value('user', user)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When you visit the index page the middleware function `get_session` will run first
|
||||||
|
This function sets a `User` value to a key `'user'`.
|
||||||
|
We get this key in `index` and display it to the user if the `'user'` key exists.
|
||||||
|
|
||||||
|
#### Changing Context values
|
||||||
|
|
||||||
|
By default context values are immutable when retrieved with `get_value`. If you want to
|
||||||
|
change the value later you have to set it again with `set_value`.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```v ignore
|
||||||
|
fn change_user(mut ctx vweb.Context) bool {
|
||||||
|
user := User{
|
||||||
|
session_id: '654321'
|
||||||
|
name: 'tester'
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the user
|
||||||
|
ctx.set_value('user', user)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Redirect
|
### Redirect
|
||||||
|
|
||||||
Used when you want be redirected to an url
|
Used when you want be redirected to an url
|
||||||
|
@ -239,6 +239,13 @@ fn test_redirect_middleware() {
|
|||||||
assert received.ends_with('302 Found')
|
assert received.ends_with('302 Found')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Context's
|
||||||
|
|
||||||
|
fn test_middleware_with_context() {
|
||||||
|
x := http.get('http://${localserver}/with-context') or { panic(err) }
|
||||||
|
assert x.body == 'b'
|
||||||
|
}
|
||||||
|
|
||||||
fn testsuite_end() {
|
fn testsuite_end() {
|
||||||
// This test is guaranteed to be called last.
|
// This test is guaranteed to be called last.
|
||||||
// It sends a request to the server to shutdown.
|
// It sends a request to the server to shutdown.
|
||||||
|
@ -46,6 +46,7 @@ fn main() {
|
|||||||
'/admin/': [middleware1]
|
'/admin/': [middleware1]
|
||||||
'/other/': [middleware1, middleware2]
|
'/other/': [middleware1, middleware2]
|
||||||
'/redirect': [middleware_redirect]
|
'/redirect': [middleware_redirect]
|
||||||
|
'/with-context': [context1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
eprintln('>> webserver: pid: ${os.getpid()}, started on http://localhost:${http_port}/ , with maximum runtime of ${app.timeout} milliseconds.')
|
eprintln('>> webserver: pid: ${os.getpid()}, started on http://localhost:${http_port}/ , with maximum runtime of ${app.timeout} milliseconds.')
|
||||||
@ -220,6 +221,12 @@ pub fn (mut app App) redirect_route() vweb.Result {
|
|||||||
return app.text('${result}should_never_reach!')
|
return app.text('${result}should_never_reach!')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
['/with-context']
|
||||||
|
pub fn (mut app App) with_context() vweb.Result {
|
||||||
|
a := app.get_value[string]('a') or { 'none' }
|
||||||
|
return app.text(a)
|
||||||
|
}
|
||||||
|
|
||||||
// middleware functions:
|
// middleware functions:
|
||||||
|
|
||||||
pub fn (mut app App) before_request() {
|
pub fn (mut app App) before_request() {
|
||||||
@ -256,6 +263,11 @@ fn middleware_redirect(mut ctx vweb.Context) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn context1(mut ctx vweb.Context) bool {
|
||||||
|
ctx.set_value('a', 'b')
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// utility functions:
|
// utility functions:
|
||||||
|
|
||||||
pub fn (mut app App) shutdown() vweb.Result {
|
pub fn (mut app App) shutdown() vweb.Result {
|
||||||
|
@ -12,6 +12,7 @@ import net.urllib
|
|||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
import encoding.html
|
import encoding.html
|
||||||
|
import context
|
||||||
|
|
||||||
// A type which don't get filtered inside templates
|
// A type which don't get filtered inside templates
|
||||||
pub type RawHtml = string
|
pub type RawHtml = string
|
||||||
@ -144,6 +145,7 @@ pub struct Context {
|
|||||||
mut:
|
mut:
|
||||||
content_type string = 'text/plain'
|
content_type string = 'text/plain'
|
||||||
status string = '200 OK'
|
status string = '200 OK'
|
||||||
|
ctx context.Context = context.EmptyContext{}
|
||||||
pub:
|
pub:
|
||||||
// HTTP Request
|
// HTTP Request
|
||||||
req http.Request
|
req http.Request
|
||||||
@ -377,6 +379,30 @@ 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 { '' }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set_value sets a value on the context
|
||||||
|
pub fn (mut ctx Context) set_value(key context.Key, value context.Any) {
|
||||||
|
ctx.ctx = context.with_value(ctx.ctx, key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get_value gets a value from the context
|
||||||
|
pub fn (ctx &Context) get_value[T](key context.Key) ?T {
|
||||||
|
if val := ctx.ctx.value(key) {
|
||||||
|
match val {
|
||||||
|
T {
|
||||||
|
// `context.value()` always returns a reference
|
||||||
|
// if we send back `val` the returntype becomes `?&T` and this can be problematic
|
||||||
|
// for end users since they won't be able to do something like
|
||||||
|
// `app.get_value[string]('a') or { '' }
|
||||||
|
// since V expects the value in the or block to be of type `&string`.
|
||||||
|
// And if a reference was allowed it would enable mutating the context directly
|
||||||
|
return *val
|
||||||
|
}
|
||||||
|
else {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
|
||||||
pub type DatabasePool[T] = fn (tid int) T
|
pub type DatabasePool[T] = fn (tid int) T
|
||||||
|
|
||||||
interface DbPoolInterface {
|
interface DbPoolInterface {
|
||||||
@ -642,6 +668,7 @@ fn handle_conn[T](mut conn net.TcpConn, global_app &T, routes &map[string]Route,
|
|||||||
|
|
||||||
// Create Context with request data
|
// Create Context with request data
|
||||||
ctx := Context{
|
ctx := Context{
|
||||||
|
ctx: context.background()
|
||||||
req: req
|
req: req
|
||||||
page_gen_start: page_gen_start
|
page_gen_start: page_gen_start
|
||||||
conn: conn
|
conn: conn
|
||||||
|
Loading…
Reference in New Issue
Block a user