Skip to main content

· 4 min read
Libing Chen

越来越多的同学开始陆续使用ChatGPT服务,它可以极大地提升开发者的工作效率,所以我们也在httpx中增加了对ChatGPT的支持,样例代码如下:

### ChatGP demo
#@name chatgpt
CHATGPT https://api.openai.com/v1/chat/completions
X-OPENAI-API-KEY ${OPENAI_API_KEY}

You are a Java developer. Please write unit test with JUnit 5 for the following code:

```java
public class UserService {
public String findNickById(Long id) {
return "nick:" + id;
}
}

在使用ChatGPT的过程中,目前有一个不太好的用户体验,就是如何输入多行文本,同时在这些内容中还要添加对应的代码,如果你只使用ChatGPT的聊天输入框,这个问题相对来说就比较难解决啦。 一不留神就将不完全的内容发送出去啦,另外就不要说代码输入这些问题,基本上是不可能的,所以很多开发人员都在JetBrains IDE或者VS Code中提前输入好这些内容,然后再复制到ChatGPT的聊天输入框中, 进行查询,虽然能工作,但是浪费了不少时间。

httpx 0.41版本中增加了ChatGPT的支持,你只要在http文件中使用CHATGPT method,当然查询的内容就是HTTP Body,不同的时这里默认将Markdown作为输入内容格式,这样你可以非常方便地输入文本和代码, 而且还有对应的代码高亮,截屏如下:

ChatGPT

通过这种方式,你非常容易进行多行文本和代码的输入,然后点击一下运行按钮就可以进行ChatGPT查询。我们都知道ChatGPT API返回的内容为JSON格式,考虑到阅读的方便,我们需要将返回结果中的答案提取出来, 然后放到返回结果后面进行显示,这样主要是方便阅读,截屏如下:

ChatGPT

我们都知道ChatGPT的message类型包括三种,分别是:system, user和assistant。 如果你想要实用该特性,你需要通过Markdown Attribute对Paragraph(段落)进行标记, 也就是在段落末尾添加{.system}{.assistant}标识,如下:

### ChatGPT with JBang
CHATGPT https://api.openai.com/v1/chat/completions

You are to generate Java code in the style of jbang, and main class must be named Hello. {.system}

Build a CLI app with Picocli 4.7.3 library, and include name and email options. Do not add any additional text.

Please use Java 17. {.assistant}

通过这种方式,你可以非常容易编写Prompt,然后进行测试,然后再进行调整,最后确保Prompt的质量,这个也是Prompt Engineering所提倡的。

提示: 如果你已经设置了OPENAI_API_KEY环境变量,那么你就可以不需要设置X-OPENAI-API-KEY HTTP头。

· 2 min read
Libing Chen

httpx 0.40版本发布,该版本主要是做减法,删除了一些不常用的功能,主要就是从OpenAPI生成http文件和从http文件生成对应语言的文件。

从OpenAPI生成http文件

OpenAPI Generator 6.3.0版本添加了jetbrains http client generator功能, 其实就是生成http文件,现在你只要调用openapi-generator-cli generate -i http://localhost:8080/v3/api-docs -g jetbrains-http-client -o openapi-testing命令行就可以基于OpenAPI生成http文件。

考虑到OpenAPI Generator的全面的功能,所以httpx不再提供从OpenAPI生成http文件的功能了。

从http文件生成对应语言的文件

这个功能并不常用,此外httpx已经提供了不同语言的http文件集成,这就包括JavaScript, Java, Python和Rust,所以这个基于http文件生成对应语言的文件的功能就显得多余。 此外考虑到大多数都在使用OpenAPI,OpenAPI Generator已经提供了从OpenAPI生成对应语言的文件的功能,所以httpx就更没有必要提供代码生成功能啦。

· 3 min read
Libing Chen

tRPC已经吸引越来越多的开发者注意,正如其口号End-to-end typesafe APIs made easy。虽然目前主流是HTTP REST API和GraphQL,但是考虑到tRPC的优势,httpx增加了对tRPC的支持。

tRPC请求介绍

理论上来说,tRPC是基于HTTP和WebSocket的,所以你完全可以使用httpx的HTTP测试功能来测试tRPC,如你要测试一个tRPC query,你可以这样做:

### tRPC query
GET http://localhost:2022/greeting.hello?input=%7B%22name%22%3A%22world%22%7D

对应查询来说哦,tRPC会将输入进行URL Encode编码,然后添加到URL中,作为input查询参数。

如果是tRPC mutate,那就更简单啦,就是普通的POST请求,样例如下:

### trpc mutation
POST http://localhost:2022/post.createPost
Content-Type: application/json

{
"title": "hello world",
"text": "check out https://tRPC.io"
}

关于tRPC HTTP通讯的详细介绍,可以参考HTTP RPC Specification该规范。

httpx的tRPC支持

上述的tRPC查询列子来说,如果使用HTTP GET进行测试,还是有点麻烦,如果要进行URL Encode,而且编码后的内容也不容易读懂,所以httpx添加了tRPC支持,让该操作进行简化,如下:

### trpc query
#@name trpc-query
TRPC http://localhost:8080/greeting.hello
Content-Type: application/json

{
"name": "world"
}

这样测试就方便多啦。 tRPC通讯的所有数据都是基于JSON类型的,所以不要忘记添加Content-Type: application/json HTTP头,这样可以让参数更加清晰,即便input参数只是一个简单字符串,如下:

### trpc query2
TRPC http://localhost:8080/greeting.hello2
Content-Type: application/json

"linux_china"

tRPC的mutate请求使用POST进行测试也不复杂,但是考虑到一致性,所以还是使用tRPC的专用请求方式,如下:

### trpc mutate
#@name trpc-mutate
TRPCM http://localhost:8080/poster.createPost
Content-Type: application/json

{
"title": "hello world",
"text": "check out https://tRPC.io"
}

注意:tRPC的mutate请求使用的是TRPCM请求方法,而query请求方法为TRPC,这个不要忘记啦。

· 2 min read
Libing Chen

最新的IntelliJ IDEA 2022.3 EAP 4:版本, HTTP Client有了最新一些特性更新,主要是Code Style和Pre-request scripts,这里稍微介绍一下。

Code style improvements for the HTTP Client

在HTTP服务测试中,经常会遇到URL中参数比较多的场景,这个时候输入参数就比较麻烦,而新的格式就简单多啦,可以非常方便地管理各个参数,样式如下:

GET http://localhost:8080/api/html/get?
name=xxx&
vip=true

Pre-request scripts and new APIs for JavaScript handlers

在一些HTTP请求场景中,一些参数可能是动态的,所以我们希望能在执行请求前执行一些脚本,完成对应的参数赋值。 现在你只要在请求Method前添加一段JavaScript脚本,就可以完成对应的逻辑,如下:

### get user info
< {%
request.variables.set("name", "linux_china");
%}
GET http://localhost:8080/api/user/{{name}}

考虑到经常要使用到一些加密相关的逻辑,所以新的版本中增加了сrypto API的支持,样例如下:

сrypto API

这样在经常涉及到请求签名的场景中,直接调用сrypto API就可以啦。

另外考虑到Mock的场景,新版本还增加了random API的支持,样例如下:

Random API

这样使用mock数据进行服务测试也简单多啦。

httpx命令行也做了相应的更新,在最新的0.37.1版本中,已经增加了这些特性的支持。

· One min read
Libing Chen

最新的Bun v0.1.11版本,添加了Plugin API的支持, 其中一项功能就是支持自定义资源加载器Loader,这个也是esbuild非常强大的地方。 所以Bun也遵循了esbuild's plugin API规范,样例代码如下:

import { plugin } from "bun";
import { renderToStaticMarkup } from "react-dom/server";

// Their esbuild plugin runs in Bun (without esbuild)
import mdx from "@mdx-js/esbuild";
plugin(mdx());

// Usage
import Foo from "./bar.mdx";
console.log(renderToStaticMarkup(<Foo />));

现在你也可以在Bun中直接到导入http文件,通过一下npm install -D bun-plugin-httpfile添加一下bun-plugin-httpfilepackage,然后样例代码如下:

import { plugin } from "bun";

import httpfilePlugin from 'bun-plugin-httpfile';
plugin(httpfilePlugin());

// Usage
import {myIp} from "./index.http";
let response = await myIp();
console.log(await response.text());

最后项目地址: https://github.com/servicex-sh/bun-plugin-httpfile 欢迎Issue和PR。

· 3 min read
Libing Chen

在esbuild, Rollup.js, Vite.js, webpack完成对httpfile直接import支持后,httpfile-py也跟着到来啦,你也可以直接在Python代码中直接import http文件,先看代码:

import httpfile.loader
# noinspection PyUnresolvedReferences
import httpbin
from httpx import Response

if __name__ == '__main__':
r: Response = httpbin.my_ip()
print(r.json())

当然你要引入httpfile-py开发包,你可以在requirements.txt直接添加一下httpfle-py即可。 首先就是在要Python文件开头添加import httpfile.loader,这个主要时就是一个Python import的自定义hook,用于支持http文件的import。 接下来就是导入具体的http文件,如import httpbin就表示导入httpbin.http文件,该http文件会包括具体的http请求,代码如下:

### get my ip
//@name my-ip
GET https://httpbin.org/ip
User-Agent: curl/7.47.0

### post test
//@name post-test
POST https://{{host}}/post
User-Agent: curl/7.47.0
Content-Type: application/json

{
"name": "{{nick}}",
"age": 42,
"uuid": "{{$uuid}}",
"demo": "hi` morning"
}

接下来就是调用指定的http请求,如r: Response = httpbin.my_ip(),其中my_ip就是http文件中对应的请求名称, 而r对应的类型为Response,这个其实就是Python httpx开发包的from httpx import Response类型,httpfile-py背后使用httpx作为HTTP Client。

最后就是调用Response提供的方法,如r.json()表示返回json数据类型,然后就可以操作返回的数据了,看一下最终的效果截屏:

httpfile-py

考虑到Python的异步Async支持,如果你想调用异步的http请求,只要在请求名称添加上async_前缀,如async_my_ip(),就表示调用异步的http请求,样例代码如下:

import httpfile.loader
# noinspection PyUnresolvedReferences
import index
import asyncio
from httpx import Response


async def my_ip():
r: Response = await index.async_my_ip()
print(r.json())


if __name__ == '__main__':
asyncio.run(my_ip())

注意:不要忘记添加asynciopackage。

最后项目地址: https://github.com/servicex-sh/httpfile-py 欢迎Issue和PR。

· 6 min read
Libing Chen

在介绍httpfile文件和Vite, esbuild, Rollup整合后,接下来就应该轮到Sprig框架啦 :)

Spring 6.0 HTTP Interface

Spring 6.0 HTTP Interface 是Spring 6.0一个非常核心的特性。 你不需要使用WebClient这类HTTP客户端开发包进行HTTP请求调用,而是调整为基于Interface接口 + Annotation方式,样例代码如下:

@HttpExchange("https://httpbin.org")
public interface HttpBinClient {
@GetExchange("/ip")
MyIp myIp();

@PostExchange("/post")
Mono<PostResponse> post(@RequestBody String body);

record MyIp(String origin) {
}

record PostResponse(String url, Map<String, String> headers, String data) {
}
}

通过声明HTTP Interface方式,我们不用再编写调用HTTP Client API相关的代码,而且也不用关心具体使用哪个HTTP SDK,WebClient、OkHttp等都可以随意切换。

接下来就是初始化HTTP Interface,我们只要选择某一HTTP Client的适配,然后使用httpServiceProxyFactory创建基于HTTP Interface的Java Proxy,代码如下:

WebClient webClient = WebClient.builder().build();
HttpServiceProxyFactory httpServiceProxyFactory = new HttpServiceProxyFactory(WebClientAdapter.forClient(webClient));
httpServiceProxyFactory.afterPropertiesSet();
HttpBinClient httpBinClient = httpServiceProxyFactory.createClient(HttpBinClient.class);
System.out.println(httpBinClient.myIp().origin());

httpfile for Spring

在实际的开发中,我们通常会创建http文件进行对应的服务测试,这样是确保服务正常运行,也方便你了解对应的服务接口。 而且如果后续服务有什么问题,你点击一下就可以重新测试服务,非常简单明了。

### get my ip
//@name myIp
GET https://httpbin.org/ip

### Post test
//@name postTest
POST https://httpbin.org/post
Content-Type: application/json

{
"hello": "{{nick}}"
}

既然已经写了http文件,那么能否服用该文件,直接将其用到HTTP服务调用上呢?借鉴Spring 6.0 HTTP Interface的思想,我们稍微进行了一些调整,样例代码如下:

@HttpFile("httpbin.http")
public interface HttpBinService {
@HttpRequestName("myIp")
MyIp myIp();

@HttpRequestName("postTest")
PostResponse postTest(String nick);

@HttpRequestName("graphqlTest")
PostResponse graphqlTest(String nick);

record MyIp(String origin) {
}

record PostResponse(String url, Map<String, String> headers, String data) {
}
}

稍微说明一下: 我们使用@HttpFile注解将Interface和http文件进行关联。在具体的API上,我们使用@HttpRequestName注解,将接口方法和http文件中的请求名称进行关联。

接下来同样是HTTP Interface接口初始化,这个和Spring 6.0的HTTP Interface初始化方式是一样的,代码如下:

HttpFileProxyFactory httpFileProxyFactory = new HttpFileProxyFactory(WebClientAdapter.forClient(WebClient.builder().build()));
HttpBinService httpBinService = httpFileProxyFactory.createClient(HttpBinService.class);
System.out.println(httpBinService.myIp().origin());

使用httpfile方式后,HTTP interface精简非常多,你不要再关心@PostExchange@RequestHeader@RequestBody等Annotation的使用,这些交给http文件处理就可以啦。

再回到具体的调用方式,如HTTP POST,你需要构建对应的body,然后再以@RequestBody String body函数参数方式添加到API声明中,最后你要在代码进行对应的body构建。

而httpfile方式,你不需要构建body,body的内容已经在http文件中进行定义啦,你只要传递对应的模板变量就可以,当然这些变量都是通过API函数的参数传递的, 也就是你看到到PostResponse postTest(String nick);这样方式,其中nick参数就http文件中的模板变量。

看一下最终的效果截屏:

httpfile Spring

考虑到http文件是支持GraphQL测试的,所以httpfile spring增加了对GraphQL的支持,GraphQL over HTTP请求如下:

### graphql test
//@name graphqlTest
GRAPHQL https://localhost:8787/graphql

query {
welcome(name : "{{nick}}" )
}

然后你声明一下GraphQL请求,GraphQL的响应json主要包括data, extensionserrors,所以创建一个GraphqlResponse record即可,Java代码如下:

@HttpFile("httpbin.http")
public interface HttpBinService {

@HttpRequestName("graphqlTest")
GraphqlResponse graphqlTest(String nick);

record GraphqlResponse(Map<String, Object> data, Map<String, Object> extensions, List<Object> errors) {
}
}

总结

对比Spring 6.0的HTTP Interface,http文件方式在某些场景更简单和灵活,如HTTP body为JSON时,你只要使用模板变量方式即可,没有必要去构建整个json数据,然后再以body方式提交HTTP请求。 当然httpfile文件也有一些缺点,如http body是完全动态的,那么这个时候可能就不太合适啦,你需要在代码中去构建body,然后再以body方式提交HTTP请求。

总的来说,绝大多数的HTTP调用场景,你通过httpfile + Interface关联方式就可以解决,关键是非常简单明了。而且其他的场景,你可以选择HTTP Interface声明方式,当然你直接调用HTTP Client也没有问题。

最后项目仓库地址: https://github.com/servicex-sh/httpfile-spring 欢迎Issue和PR。

· 6 min read
Libing Chen

在日常开发中,调用HTTP REST API或者GraphQL服务,这些都是少不了的,毕竟大多数应用还是要和网络服务进行交互的。 如果你使用JetBrains IDE或者VS Code,最简单的方式就是创建一个index.http文件,然后在IDE中点击一下就可以运行并看到对应的测试结果。

HTTP Requests

在没有打开IDE的情况下,你还可以使用httpx命令行进行测试。如果要修改一下测试的数据,直接打开http文件,数据修改后再点击一下按钮运行即可,非常方便。

虽然服务测试这些都完成啦,但是你还有至关重要的代码没有写,当然这些代码并不难,如你使用fetch,样例代码如下:

const data = {
"nick": "linux_china",
"email": "[email protected]",
"age": 25
};
const response = await fetch('https://httpbin.org/post', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})

console.log(await response.json());

当然有些同学说我要使用axios,这样的HTTP Client非常多,就看你的个人喜好啦。代码虽然不多,也不复杂,但是还有一定的工足量,而且这些代码看起来都像是重复代码。

能否有一种更好的方法,能否基于http文件自动完成这些格式代码的生成? 如果能像esbuild的loader那样,直接引用就可以啦,样例代码如下:

import {myIp} from "./index.http";

let response = await myIp();
console.log(await response.json());

上述的代码简单明了多啦,我们根本不用关系HTTP通讯的细节,使用fetch还是axios,也不要写那么多重复的代码,完全就是一个函数调用就搞定啦。 如果http文件中对应的请求包括变量,那么我们只要在请求时将这些变量传入即可,如下:

import {postTest} from "./index.http";

response = await postTest({nick: "test", host: "httpbin.org", "uuid": "c8389930-1071-4b88-9676-30b9ba7f2343"});
console.log(await response.json());

最后我们只要在esbuild配置代码中添加一下httpfile对应的esbuild plugin,这样就可以自动完成http文件到JavaScript代码的转换,如下:

import esbuild from 'esbuild';
import httpfilePlugin from 'esbuild-plugin-httpfile';

esbuild.build({
bundle: true,
entryPoints: ['hello.mjs'],
plugins: [httpfilePlugin(true)],
platform: 'node',
format: "esm",
write: true,
outfile: "bundle.mjs"
}).then(result => {
console.log(result.outputFiles[0].text);
}).catch(() => process.exit(1));

当然背后的原理也不复杂,就是esbuild的自定义loader,在import http文件时自动完成http文件到JavaScript代码的转换,非常多的esbuild plugin也都是这样做的。

借助该方式,我们测试服务简单啦,在IDE中点击一下就可以,当然命令行也能测试,此外该http文件还可以做为其他JS文件的module,负责处理http通讯的细节,可谓是一鱼多吃。

此外这种方式排查问题也方便,如果服务调用有问题,你只要打开http文件测试一下就可以,而不是像之前那样去看应用中的JavaScript代码,如果涉及代码调整,还要涉及反复的单元测试。

此外使用这种http文件loader方式,还可以很好地解决请求Mock的问题。如用户登录的API还没有开发完成,但是你要在程序要使用该接口,你不需要改任何代码, 你只要在http文件中对应的请求添加//@mock tag,表示请求时直接返回该模拟数据,样例如下:

### user login
//@name login
//@mock {"success": true}
POST https://your_domain_com/user/login
Content-Type: application/json

{
"nick": "your_nick_name",
"password": "123456"
}

如果是多行数据,添加多个//@mock tag,如下:

### get csv data
//@name myData
//@mock name,gender
//@mock linux_china,M
GET https://your_service/data
Accept: text/csv

当REST API上线后,你只要将请求中的//@mock tag删除即可,这样就可以正常的调用REST API了。 Note: 如果在上线前你忘记删除//@mock tag也没有关系,如果process.env.NODE_ENV的值为production,mock是会被忽略的。

esbuild的httpfile插件地址: https://github.com/servicex-sh/esbuild-plugin-httpfile 目前整合esbuild的框架非常多,这些框架都可以使用该插件。 如果是其他框架,也可以自己集成,并不复杂, 另外可以参考Rollup的httpfile插件,地址为: https://github.com/servicex-sh/rollup-plugin-httpfile

· 3 min read
Libing Chen

在一些通讯协议中存在者双向通讯的场景,也就是常说的Channel,既可以向Channel中发送消息,也可以从Channel中接收消息,如WebSocket, RSocket Channel等,都是这一通讯模式。 这种交互性的通讯模式,存在者发送多个消息,而且在某些场景下可能还存在者消息的等待和发送顺序,对测试来说是比较麻烦的。 那么在JetBrains HTTP Client中是如何进行Channel的消息发送和消费的呢? 让我们看一个WebSocket的样例:

### WebSocket interactive request
WEBSOCKET ws://{{$exampleServer}}/ws
Content-Type: application/json

===
{
"message": "Hello, server!"
}
=== wait-for-server
=== wait-for-server
{
"message": "We send this message..."
}
===
{
"message": "And this message together"
}

这里稍微解释一下WebSocket请求的流程:

  • 首先是创建WebSocket连接,这个应该是没有什么问题的
  • WebSocket长连接创建完毕后,我们马上发送第一个消息给服务端,也就是上面的"Hello, server!"消息
  • 接下来我们看到的消息分隔符号=== wait-for-server,表示我们要等待WebSocket服务端回发的消息
  • 接下来还是消息分隔符号=== wait-for-server,表示要继续等待WebSocket服务端回发的消息
  • 在收到WebSocket服务端回发的两个消息后,我们接下来就是连续发生两个消息,分别是"We send this message...""And this message together"
  • 两个消息发送完毕后,接下来就是继续等待WebSocket服务端回发的消息,直到WebSocket连接被开发者主动关闭,如JetBrains IDE中点击close按钮。

借助消息分隔符===,我们可以向channel中发送多个消息,当然如果你需要等待服务端返回消息后再发送,那么消息分隔符号后添加一个 wait-for-server,这样HTTP Client就会等待服务端返回消息后再发送。

当然消息分隔符的设计,对命令行测试也非常友好,在图形化界面,如JetBrains IDE中,你可以使用图形化的界面进行交互式的消息输入,但是在命令行模式下,这个输入是非常麻烦的, 相反这种消息分隔符方式更编译命令行的测试。 httpx 0.35.0版本添加了WEBSOCKET method支持,这样你在命令行模式下也可以进行WEBSOCKET服务测试。