HttpServer 简介
HttpServer属于高级开发指南的内容,但是在开发使用大模型API的应用(例如MCP服务)时,可能需要使用到Web服务,所以这里简单介绍一下HttpServer,其更详细的使用可参考高级开发指南。
HttpServer是 Foxtable 内置的一个全局变量,作用是启动一个 HTTP 服务。启动后,浏览器(或任何 HTTP
客户端)访问这个端口,Foxtable 就会触发 HttpRequest 事件,你在事件里写代码决定返回什么。
一句话流程:启动 HttpServer → 浏览器发请求 → 触发 HttpRequest 事件 → 你写代码响应
最小示例
启动代码(可放在按钮事件Click、项目事件AfterOpenProject里):
HttpServer.Prefixes.Add("http://127.0.0.1/")
HttpServer.Start()
响应代码(网络监视器 → HttpRequest 事件):
e.WriteString("Hello World")
在浏览器输入地址 "http://127.0.0.1/",页面显示"Hello World"。
重要提示:必须以管理员身份启动Foxtable,才能开启Web服务。
启动与关闭服务
| 成员 | 类型 | 说明 |
|---|---|---|
| HttpServer.Prefixes.Add(地址) | 方法 | 绑定 IP 和端口 |
| HttpServer.Start() | 方法 | 启动服务 |
| HttpServer.Close() | 方法 | 关闭服务 |
| HttpServer.IsRunning | 属性 | 是否正在运行 |
例如:
If
HttpServer.IsRunning
Then
HttpServer.Close()
End
If
HttpServer.Prefixes.Clear()
'清空原来绑定的IP
HttpServer.Prefixes.Add("http://127.0.0.1/")
HttpServer.Start()
绑定地址的写法:
'
默认端口
80,浏览器访问可省略端口号
HttpServer.Prefixes.Add("http://*/")
' 只允许本机访问
HttpServer.Prefixes.Add("http://127.0.0.1/")
' 只允许局域网访问
HttpServer.Prefixes.Add("http://192.169.10.100/")
'
指定端口,末尾斜杠不可省略
HttpServer.Prefixes.Add("http://*:32177/")
可以一次绑定多个地址:
HttpServer.Prefixes.Add("http://*/")
HttpServer.Prefixes.Add("http://*:32177/")
HttpServer.Prefixes.Add("http://192.168.0.100:32188/")
HttpServer.Start()
静态文件服务
| 成员 | 说明 |
|---|---|
| HttpServer.WebPath | 指定静态文件目录,目录中已存在的文件会被自动发送 |
| HttpServer.Extensions | 管理可自动发送的文件后缀名(必须小写、以`.` 开头) |
设置 WebPath 后,请求的文件若在该目录下存在,系统直接发送,不再触发 HttpRequest
事件。
HttpServer.Prefixes.Add("http://*/")
HttpServer.WebPath =
"d:\web"
'
静态文件目录
HttpServer.Start()
此时访问"http://127.0.0.1/logo.png",若"d:\web\logo.png" 存在,会直接返回该图片,不再触发 HttpRequest
事件
通过 Extensions 管理可发送的后缀名,例如:
HttpServer.Extensions.Remove(".doc")
'
不再自动发送
.doc
HttpServer.Extensions.Add(".table")
'
允许自动发送
.table
默认支持:.jpg .gif .png .bmp .js .css .html .htm .zip .rar .txt .json .svg .doc
.docx .xls .xlsx 等。
HttpRequest 事件e参数成员总览
HttpRequest事件的e参数 为RequestEventArgs类型,下面的两个表格列出了其全部的属性和方法,后面会逐个细讲。
| 属性 | 类型 | 说明 |
|---|---|---|
| e.Path | String | 请求路径,分隔符是 `\` |
| e.Host | String | 客户端访问的 IP 或域名 |
| e.Port | Integer | 客户端访问的端口号 |
| e.Values | 字典 | GET + POST 的全部提交数据 |
| e.GetValues | 字典 | 仅 GET 方式的数据 |
| e.PostValues | 字典 | 仅 POST 方式的数据 |
| e.PlainText | String | 收到的纯文本数据 |
| e.Files | 字典 | 上传的文件 |
| e.Cookies | 字典 | 收到的 Cookie |
| e.Headers | 字典 | 客户端请求头 |
| e.ResponseHeaders | 字典 | 设置的响应头 |
| e.StatusCode | Integer | HTTP 状态码 |
| e.CacheTime | Integer | 缓存时间(秒) |
| e.ResponseEncoding | String | 响应编码,默认 utf-8 |
| e.Request | HttpListenerRequest | 底层请求对象 |
| e.Response | HttpListenerResponse | 底层响应对象 |
| 方法 | 说明 |
|---|---|
| e.WriteString(text) | 发送文本(默认 html) |
| e.WriteString(text, contentType) | 发送指定内容类型的文本 |
| e.WriteFile(filePath) | 发送本地文件 |
| e.SaveFile(key, uploadFile, localFile) | 保存上传的文件 |
| e.Redirect(url) | 跳转 |
| e.AppendCookie(name, value) | 写入 Cookie |
| e.WriteDataTable(...) | 发送 DataTable |
| e.WriteBookAsHTML(...) | 发送 Excel 报表为网页 |
| e.WriteBookAsPDF(...) | 发送 Excel 报表为 PDF |
开启Web服务
接下来我们开始实例讲述,请先新建一个项目,在项目事件AfterOpenProject加上代码,用于开启Web服务并指定静态文件存储目录:
HttpServer.Prefixes.Add("http://*/")
HttpServer.WebPath
= "d:\web"
'指定静态文件存储位置
HttpServer.Start()
我们将这个含有Web服务的项目称之为"服务端项目"。
通常我们是通过浏览器访问Web服务的,但很多时候不是通过浏览器,而是通过代码访问Web服务。
需要特别注意的是:服务端项目开启WEB服务后,不可以在服务端项目通过代码访问这个Web服务,只能在其他项目通过代码访问之
所以如果要通过代码测试Web服务的话,还需要另外创建一个项目,我们称之为"客户端项目"
HttpServer和HttpRequest都是运行在服务端项目的,而用于测试web服务的HttpClient则是运行在客户端项目,接下来可能不会特别说明,切记切记。
获取请求信息
| 属性 | 类型 | 说明 |
|---|---|---|
| e.Host | String | 返回客户端访问的 IP 或域名 |
| e.Path | String |
返回请求路径,分隔符是
`\`
例如将 服务端项目的 HttpRequest事件代码设置为: e.WriteString(e.Host & vbCrLf & e.Path & vbCrLf & e.Port)
在客户端项目的命令窗口执行: |
| e.Port | Integer |
返回客户端访问的端口号 我们可以根据Host、Path和Port分发内容,例如将服务端的HttpRequest事件代码设置为:
Select
Case
e.Host |
获取请求数据
| 属性 | 类型 | 说明 |
|---|---|---|
| e.GetValues | 字典 |
仅
GET 方式的数据,是Values的子集
服务端项目的
HttpRequest事件代码设置为:
客户端命令窗口执行: |
| e.PostValues | 字典 |
仅 POST
方式的数据,是Values的子集 例如服务端HttpRequest事件代码: |
| e.Values | 字典 |
返回GET + POST
的全部提交数据,是GetValues和PostValues的合计 例如服务端HttpRequest事件代码: Select Case e.Path Case "accept.htm" Dim sb As New StringBuilder sb.AppendLine("通过GET方式提交的数据:") For Each key As String In e.GetValues.Keys sb.AppendLine(key & ":" & e.GetValues(key)) Next sb.AppendLine(vbCrLf & "通过POST方式提交的数据:") For Each key As String In e.PostValues.Keys sb.AppendLine(key & ":" & e.PostValues(key)) Next sb.AppendLine(vbCrLf & "二者合集:") For Each key As String In e.Values.Keys sb.AppendLine(key & ":" & e.Values(key)) Next e.WriteString(sb.ToString) End Select
客户端命令窗口执行: |
| e.PlainText | String |
收到的纯文本数据,当客户端以
application/json、text/plain 等类型 POST 时,请求体会落在 e.PlainText(不会进
PostValues) 例如服务端HttpRequest事件代码: Dim jo As JObject = JsonConvert.DeserializeObject(e.PlainText) Dim name As String = jo("name") Dim age As Integer = jo("age") e.WriteString("姓名:" & name & ", 年龄:" & age) 客户端命令窗口执行: Dim hc As New HttpClient("http://127.0.0.1/accept.htm") Dim jo As JObject = New JObject() From { {"name", "yang"}, {"age", 20} } hc.Content = jo.ToString() Return hc.GetData() 显示的结果为:姓名:yang, 年龄:20 |
文件的上传与下载
| 属性/方法 | 说明 |
|---|---|
| e.WriteFile(filePath) |
发送本地文件到客户端
下面是一个通过代码从服务端下载文件到客户端的例子。
客户端命令窗口执行: 执行后会将下载好的文件保存为"c:\test\focas.pdf" |
| e.Files | 字典,包括客户端上传的全部文件,键为文件上传组件的name属性,值是一个字符串集合,包括用户通过这个上传组件上传的所有文件名 |
| e.SaveFile(key, uploadFile, localFile) |
保存客户端上传的文件。 下面是一个通过代码从客户端上传文件到服务端的例子。 服务端HttpRequest事件代码: Select Case e.Path Case "upload.htm" Dim name As String = e.PostValues("name") For Each key As String In e.Files.Keys For Each fl As String In e.Files(key) e.SaveFile(key, fl, "d:\web\uploadfiles\" & fl) Next Next e.WriteString("员工[" & name & "]的资料已经上传成功") End Select
客户端命令窗口执行: 执行后,如果文件上传成功,会提示:员工[张三]的资料已经上传成功 |
发送响应
| 方法/属性 | 说明 |
|---|---|
| e.WriteString(text) |
发送文本,例如: e.WriteString("<h1>标题</h1>") |
| e.WriteString(text, contentType) |
发送指定内容类型的文本,例如: e.WriteString("{""code"":0,""msg"":""ok""}", "application/json") |
| e.WritePDFCreator(Creator, FileName, Inline) |
将PDFCreator生成的PDF文档发送到客户端,参数: Creator: 要发送的PDFCreator FileName: 客户端浏览器下载此文件时使用的文件名 InLine: 可选参数,逻辑型,是否直接在浏览器显示PDF文件,默认为True,设为False将直接下载。 服务端HttpRequest事件代码: Dim pdc As New PDFCreator() Dim rect As RectangleF = pdc.PageRectangle() rect.Inflate( - 72, - 72) ' Dim fnt As New Font("微软雅黑", 12) pdc.DrawString("Hello Foxtable!", fnt, Brushes.Black, rect) e.WritePDFCreator(pdc, "test.pdf") 客户端命令窗口执行: Dim htc As New HttpClient("http://127.0.0.1") Dim file As String = "c:\test\123.pdf" If htc.GetFile(file) Then Process.Start(file) End If 执行后会下载并显示一个pdf文件。 |
|
e.WriteWordCreator(Creator, FileName, Inline) |
将WordCreator生成的Word文档发送到客户端,参数: Creator: 要发送的WordCreator FileName: 客户端浏览器下载此文件时使用的文件名 InLine: 可选参数,逻辑型,是否直接在浏览器显示Word文件,默认为True,设为False将直接下载。
服务端HttpRequest事件代码: 执行后会下载并显示一个word文件。 |
| e.Redirect(url) |
跳转到另一个地址,例如: e.Redirect("http://www.foxtable.com") 跳转到本站页面: e.Redirect("about.htm") |
| e.StatusCode |
HTTP
状态码 一般无需设置;需要返回特定状态码时使用,例如:
e.StatusCode = 404 |
| e.CacheTime |
缓存时间(秒)。 让浏览器在指定时间内使用缓存,不再重复请求,例如: If e.Path = "logo.png" Then e.CacheTime = 3600 ' 缓存 1 小时 e.WriteFile("d:\web\logo.png") End If |
| e.ResponseEncoding |
响应编码,默认
utf-8 一般无需设置,如果网页内容是别的编码(如 `gb2312`),需显式设置,否则可能乱码。 e.ResponseEncoding = "gb2312" e.WriteString("<meta charset='gb2312'>中文内容") |
收发Cookie
| 属性 | 说明 |
|---|---|
| e.Cookies | 字典,从服务端收到的Cookie |
| e.AppendCookie(name, value) |
方法,向客户端写入Cookie 服务端HttpRequest事件代码:
Dim
Verified As
Boolean
客户端命令窗口执行: |
请求头和响应头
HttpRequest和头部信息相关的属性有:
| 属性 | 类型 | 说明 |
|---|---|---|
| e.Headers | 字典 | 客户端发来的请求头 |
| e.ResponseHeaders | 字典 | 发送到客户端的响应头 |
HttpClient也有两个完全同名的属性:
| 属性 | 类型 | 说明 |
|---|---|---|
| Headers | 字典 | 发送到服务端的的请求头 |
| ResponseHeaders | 字典 | 服务端发来的响应头 |
Headers是请求头,方向是从客户端到服务端;ResponseHeaders是响应头,方向是从服务端到客户端。
下面是个小例子,服务端HttpRequest事件代码:
Dim
author
As
String
= UrlDecode(e.Headers("author"))
'获取客户端发来的头部信息
Dim
age
As
String
= e.Headers("age")
e.ResponseHeaders.Add("brief",
URLEncode("越努力越幸运"))
'向客户端发送头部信息
e.ResponseHeaders.Add("pages",
600)
e.WriteString("你好,"
& author)
客户端命令窗口执行:
Dim
htc
As
New
HttpClient("http://127.0.0.1")
htc.Headers.Add("author",
UrlEncode("舒淇"))
'向服务端附加头部信息
htc.Headers.Add("age",
30)
Dim
ret
As
String
= htc.GetData()
Dim
brief
As
String
= UrlDecode(htc.ResponseHeaders("brief"))
'获取服务
端发来的头部信息
Dim
pages
As
Integer
= htc.ResponseHeaders("pages")
Output.Show(ret & vbCrLf & brief & vbCrLf & pages)
执行后显示的结果是:
你好,舒淇
越努力越幸运
600
异步执行
如果服务端的访问量比较大, 可以考虑异步执行HttpRequest事件代码,关于异步编程,可以参考:异步编程
要改为异步执行,可以分两步修改代码:
1、将HttpRequest事件的代码移到一个自定义函数中:
Dim
e As
RequestEventArgs = Args(0)
'原来的HttpRequest事件代码直接复制到这里,不用做任何改变
2、HttpRequest事件代码改为:
'''Async
e.AsyncExecute =
True
'通知需要异步执行或直接通过底层对象处理本次访问
Await
Functions.ExecuteAsync("自定义函数名",
e)
e.Handled =
True
'通知系统已经处理完毕
下面是一个简单的示例:
1、服务端项目新建一个自定义函数,名称为"Sum",代码为:
Dim
e
As
RequestEventArgs = Args(0)
Select
Case
e.Path
Case
"Sum.htm"
Dim
total
As
Long
= 0
For
i
As
Integer
= 1
To
100000
total = total + i
Next
e.WriteString("结果:"
& total)
End
Select
2、服务端的Httprequest事件代码为:
'''Async
e.AsyncExecute =
True
'通知需要异步执行或直接通过底层对象处理本次访问
Await
Functions.ExecuteAsync("Sum",
e)
e.Handled =
True
'通知系统已经处理完毕
3、客户端命令窗口执行:
Dim
htc
As
New
HttpClient("http://127.0.0.1/sum.htm")
Dim
ret
As
String
= htc.GetData()
Return
ret
或:
'''Async
Dim
htc
As
New
HttpClient("http://127.0.0.1/sum.htm")
Dim
ret
As
String
=
Await
htc.GetDataAsync()
Return
ret
执行后会显示"结果:5000050000"
底层对象
底层对象适合需要精细控制的专业开发者,相关属性有:
| 属性 | 类型 | 说明 |
|---|---|---|
| e.Request | HttpListenerRequest |
底层请求对象
例如通过底层对象读取请求头: |
| e.Response | HttpListenerResponse |
底层响应对象 例如用底层对象精确写字节流: e.AsyncExecute = True '如果要使用底层对象,必须首先将AsyncExecute设置为True Dim response As System.Net.HttpListenerResponse = e.Response response.ContentType = "application/json" Dim bytes As Byte() = System.Text.Encoding.UTF8.GetBytes("{""ok"":true}") response.OutputStream.Write(bytes, 0, bytes.Length) e.Handled = True '使用结束后,无需手动清理资源,将Handled属性设置为True即可 |