认识请求上下文(Context)
Handler + Context 架构,是Solon Web 的基础。在 Context (org.noear.solon.core.handle.Context)里可以获取:
- 请求相关的对象与接口
 - 会话状态相关的对象与接口
 - 响应相关的对象与接口
 
或者理解所有请求与响应相关的,都在它身上。关于架构方面,可以再看看《想法与架构笔记》
1、几种获取(或控制) Context 的方式
a) 通过 Controller 获取
@Controller
public class HelloController{
    @Mapping("/hello")
    public String hello(Context ctx){
        //可以注入 ctx:Context
        return "Hello " + ctx.paramOrDefault("name", "world");
    }
}
b) 通过 Handler 或 Filter 或 RouterInterceptor 接口方式获取
Solon.start(DemoApp.class, args, app->{
   app.get("/hello", ctx-> ctx.output("Hello " + ctx.paramOrDefault("name", "world"))); 
});
//或者,用以组件方式编写
@Mapping("/hello")
@Component
public class HelloHandler implements Handler{
    public void handle(Context ctx) throws Throwable{
        ctx.output("Hello " + ctx.paramOrDefault("name", "world"));
    }
}
c) 直接获取当前上下文(基于 ThreadLocal 实现)
Context ctx = Context.current();
d) 获取当前线程的上下文,进行跨线程传递(一般在异步场景才用到)
public class Demo {
    public void asyncDemo() {
        //获取当前线程的请求上下文
        Context ctx = Context.current();
        //跨线程传递当前请求上下文
        RunUtil.async(()->{
            ContextHolder.currentSet(ctx);
             
            try {
                //... Context.current() 有值...
            } finally {
                //用完要移掉(很重要)
                ContextHolder.currentRemove();
            }
        });
    }
}
e) 设置空的当前上下文,并模拟数据(一般用在非 web 请求环境)
Context ctx = ContextEmpty.create();
//模拟请求的数据(如果有需要)
ctx.headerMap().put("Header1", "1"); 
ctx.paramMap().put("Param1", "1"); 
ctx.pathNew("/path1")
ContextHolder.currentSet(ctx);
try {
    //... Context.current() 有值...
} finally {
    //用完要移掉(很重要)
    ContextHolder.currentRemove();
}
2、几个特殊属性
| 属性 | 说明 | 备注 | 
|---|---|---|
| ctx.mainHandler() | 获取主请求处理 | 路由器没有登记时,则为 null(基本属于 404 了) | 
| ctx.controller() | 获取当前控制器实例 | 如果不是 MVC 处理,则为 null | 
| ctx.action() | 获取当前动作对象 | 如果不是 MVC 处理,则为 null | 
| ctx.action().method() | 获取当前动作的执行函数包装 | |
| ctx.errors | 获取MVC处理异常 | action 执行完成后才会有值 | 
| ctx.result | 获取MVC处理结果 | action 执行完成后才会有值 | 
| ctx.attr("output") | 获取 string 输出内容 | 只要通过 ctx.output(str) 输出的都会有这个记录 | 
提示:控制器里的 @Mapping 函数,即为 Action。
3、请求相关的接口
| 请求相关接口 | 说明 | 
|---|---|
| -request()->Object | 原始请求对象 | 
| -remoteIp() | 获取远程ip(也可能是代理的ip) | 
| -remotePort() | 获取远程端口(也可能是代理的port) | 
| -realIp()->String | 获取客户端真实IP | 
| -isMultipart()-bool | 是否为分段内容 | 
| -isMultipartFormData()->bool | 是否为分段表单数据 | 
| -method()->String | 获取请求方式 | 
| -protocol()->String | 获取请求协议 | 
| -protocolAsUpper()->String | 获取请求协议并大写 | 
| -url()->String | 获取请求的URL字符串 | 
| -uri()->URI | 获取请求的URI | 
| -path()->String | 获取请求的URI路径 | 
| -pathNew(String) | 设置新路径 | 
| -pathNew()->String | 获取新路径,不存在则返回原路径 | 
-pathMap(String)->Map<String,String> | 获取请求的URI路径变量,根据路径表达式 | 
| -pathAsUpper()->String | 获取请求的URI路径并大写 | 
| -pathAsLower()->String | 获取请求的URI路径并小写 | 
| -userAgent()>String | 获取请求的UA | 
| -contentLength()->long | 获取内容长度 | 
| -contentType()->String | 获取内容类型 | 
| -queryString()->String | 获取查询字符串 | 
| -accept()->String | 获取 Accept 头信息 | 
| -body()->String | 获取body内容 | 
| -body(String)->String | 获取body内容,并按指定字符串解码 | 
| -bodyNew()->String | 获取新的body | 
| -bodyNew(String) | 设置新的body | 
| -bodyAsBytes()->byte[] | 获取body内容为byte[] | 
| -bodyAsStream()->InputStream | 获取body内容为Stream | 
-paramValues(String)->String[] | 获取参数数组 | 
| -param(String)->String | 获取参数 | 
| -paramOrDefault(String, String)->String | 获取参数,并给定默认值 | 
| -paramAsInt(String)->int | 获取参数并转为int | 
| -paramAsInt(String, int)->int | 获取参数并转为int, 并给定默认值 | 
| -paramAsLong(String)->long | 获取参数并转为long | 
| -paramAsLong(String, long)->long | 获取参数并转为long,并给定默认值 | 
| -paramAsDouble(String)->double | 获取参数并转为double | 
| -paramAsDouble(String, double)->double | 获取参数并转为double,并给定默认值 | 
| -paramAsDecimal(String)->BigDecimal | 获取参数并转为BigDecimal | 
| -paramAsDecimal(String, BigDecimal)->BigDecimal | 获取参数并转为BigDecimal,并给定默认值 | 
-paramAsBean(Class<T>)->T | 获取参数并转为Bean | 
-paramMap()->MultiMap<String> | 获取所有参数集合 | 
-paramNames()->Set<String> | 获取所有参数名字集合 | 
| -file(String)->UploadedFile | 获取上传文件,第一个 | 
-fileMap()->MultiMap<UploadedFile> | 获取所有上传文件集合 | 
-fileValues(String)->UploadedFile[] | 获取上传文件,可能有多个 | 
-fileNames()->Set<String> | 获取所有上传文件名字集合 | 
| -filesDelete() | 删除所有上传的临时缓冲文件(如果有) | 
| -cookie(String)->String | 获取 cookie | 
| -cookieOrDefaul(String, String)->String | 获取 cookie, 并给定默认值 | 
-cookieValues(String)->String[] | 获取 cookie 数组 | 
-cookieMap()->MultiMap<String> | 获取所有 cookie 集合 | 
-cookieNames()->Set<String> | 获取所有 cookie 名字集合 | 
| -header(String)->String | 获取 header | 
| -headerOrDefault(String, String)->String | 获取 header,并给定默认值 | 
-headerValues(String)->String[] | 获取 header 数组 | 
-headerMap()->MultiMap<String> | 获取所有 header 集合 | 
-headerNames()->Set<String> | 获取所有 header 名字集合 | 
关于 MultiMap 接口的使用示例:
//for(遍历)
for(KeyValues<String> kv: ctx.paramMap()){ //headerMap(), cookieMap()
    String       val  = kv.getValue();  //单值
    List<String> val2 = ky.getValues(); //多值
}
//add(添加)
ctx.paramMap().add("list", "a1")
ctx.paramMap().add("list", "a2")
ctx.paramValues("list") -> String[] //获取多值
//put(替换)
ctx.paramMap().put("item", "a1")
ctx.param("item") -> String //获取单值
4、响应相关的接口
| 响应相关接口 | 说明 | 
|---|---|
| -response()->Object | 原始响应对象 | 
| -charset(String) | 设置字符集 | 
| -contentType(String) | 设置内容类型 | 
| -contentTypeNew() | 获取设置的新内容类型 | 
| -contentLength(long) | 设置内容长度 | 
| -render(Object) | 渲染数据(比如将对象渲染为 Json 并输出) | 
| -render(String, Map) | 渲染视图 | 
| -renderAndReturn(Object)->String | 渲染数据并返回 | 
| -output(byte[]) | 输出 字节数组 | 
| -output(InputStream) | 输出 流对象 | 
| -output(String) | 输出 字符串 | 
| -output(Throwable) | 输出 异常对象 | 
| -outputAsJson(String) | 输出为json文本 | 
| -outputAsHtml(String) | 输出为html文本 | 
| -outputAsFile(DownloadedFile) | 输出为文件 | 
| -outputAsFile(File) | 输出为文件 | 
| -outputStream()->OutputStream | 获取输出流 | 
| -outputStreamAsGzip()->GZIPOutputStream | 获取压缩输出流 | 
| -flush() | 冲刷 | 
| -headerSet(String, String) | 设置响应 header | 
| -headerAdd(String, String) | 添加响应 header | 
| -headerOfResponse(String) | 获取响应 header | 
| -headerValuesOfResponse(String) | 获取响应 header 数组 | 
| -headerNamesOfResponse() | 获取响应 header 所有名字 | 
| -cookieSet(String, String) | 设置响应 cookie | 
| -cookieSet(String, String, int) | 设置响应 cookie | 
| -cookieSet(String, String, String, int) | 设置响应 cookie | 
| -cookieSet(String, String, String, String, int) | 设置响应 cookie | 
| -cookieRemove(String) | 移徐响应 cookie | 
| -redirect(String) | 302跳转地址 | 
| -redirect(String, int) | 跳转地址 | 
| -forward(String) | 服务端转换地址 | 
| -status() | 获取输出状态 | 
| -status(int) | 设置输出状态 | 
5、会话相关的接口
| 会话相关接口 | 说明 | 
|---|---|
| -sessionState()->SessionState | 获取 sessionState | 
| -sessionId()->String | 获取 sessionId | 
| -session(String)->Object | 获取 session 状态 | 
| -sessionOrDefault(String, T)->T | 获取 session 状态(类型转换,存在风险) | 
| -sessionAsInt(String)->int | 获取 session 状态以 int 型输出 | 
| -sessionAsInt(String, int)->int | 获取 session 状态以 int 型输出, 并给定默认值 | 
| -sessionAsLong(String)->long | 获取 session 状态以 long 型输出 | 
| -sessionAsLong(String, long)->long | 获取 session 状态以 long 型输出, 并给定默认值 | 
| -sessionAsDouble(String)->double | 获取 session 状态以 double 型输出 | 
| -sessionAsDouble(String, double)->double | 获取 session 状态以 double 型输出, 并给定默认值 | 
| -sessionSet(String, Object) | 设置 session 状态 | 
| -sessionRemove(String) | 移除 session 状态 | 
| -sessionClear() | 清空 session 状态 | 
6、其它查询
| 其它相关接口 | 说明 | 
|---|---|
| +current()->Context | 获取当前线程的上下文 | 
| -getLocale()->Locale | 获取地区 | 
| -setLocale(Locale) | 设置地区 | 
| -setHandled(bool) | 设置处理状态 | 
| -getHandled() | 获取处理状态 | 
| -setRendered(bool) | 设置渲染状态 | 
| -getRendered() | 获取渲染状态 | 
| -attrMap()->Map | 获取自定义特性并转为Map | 
| -attr(String)->Object | 获取上下文特性 | 
| -attrOrDefault(String, T)->T | 获取上下文特性,并设定默认值 | 
| -attrSet(String, Object) | 设置上下文特性 | 
| -attrSet(Map) | 设置上下文特性 | 
| -attrClear() | 清除上下文特性 | 
| -remoting()->bool | 是否为远程调用 | 
| -remotingSet(bool) | 设置是否为远程调用 | 
| -result:Object | 用于在处理链中透传处理结果 | 
| -errors:Throwable | 用于在处理链中透传处理错误 | 
| -controller()->Object | 获取当前控制器 | 
| -action()->Action | 获取当前动作 |