文章目录
背景
在实际开发过程中,我们经常需要调用对方提供的接口或测试自己写的接口是否合适。很多项目都会封装规定好本身项目的接口规范,所以大多数需要去调用对方提供的接口或第三方接口(短信、天气等)。
测试前准备
第一步(服务提供者)
准备两个项目:
项目A: 服务提供者
项目B:服务消费者
在项目A中做了两个接口,一个Post
方式,一个Get
方式,用于测试下面四种方式的调试
@ApiOperation(value = "测试HTTP请求(POST方式)", notes = "\n开发者:")
@PostMapping(value = "/testHttpAccess")
//@SImplePermission
public JsonResult<TestHttpAccessResponse> testHttpAccess(@RequestBody TestHttpAccessRequest request) {
return JsonResult.success(testService.testHttpAccess(request));
}
@ApiOperation(value = "测试HTTP请求(GET方式)", notes = "\n开发者:")
@GetMapping(value = "/testHttpAccessGet")
//@SImplePermission
public JsonResult<TestHttpAccessResponse> testHttpAccessGet(TestHttpAccessRequest request) {
return JsonResult.success(testService.testHttpAccess(request));
}
参数类和响应类:
@Data
@ApiModel("测试HTTP请求入参")
public class TestHttpAccessRequest {
@ApiModelProperty(value = "名字")
private String name;
@ApiModelProperty(value = "年龄")
private Integer age;
@ApiModelProperty(value = "地址")
private String address;
}
@Data
@ApiModel("测试HTTP请求响应")
public class TestHttpAccessResponse {
@ApiModelProperty(value = "名字")
private String name;
@ApiModelProperty(value = "年龄")
private Integer age;
@ApiModelProperty(value = "地址")
private String address;
}
Service
:
public TestHttpAccessResponse testHttpAccess(TestHttpAccessRequest request) {
return BeanUtil.toBean(request, TestHttpAccessResponse.class);
}
第二步(服务提供者)
为了能够演示token校验,我写了一个最简单的token
检验切面
注解@SImplePermission
,只要有这个注解才对token
进行校验,可以直接在Controller
的方法中使用
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SimplePermission {
}
切面:SimplePermissionInterceptor
,对token
进行校验
因为项目A我还有其他的切面,这个切面必须是最前面的,所以设置了order=-1
@Aspect
@Order(value = -1)
@Component
public class SimplePermissionInterceptor {
@Resource
private HttpServletRequest request;
@Resource
private AuthService authService;
@Pointcut("@annotation(com.zby.annotation.SimplePermission)")
public void pointcut() {
}
@Around("pointcut()")
public Object intercept(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
SimplePermission annotation = method.getAnnotation(SimplePermission.class);
if (Objects.nonNull(annotation)) {
String value = request.getHeader(AuthConstant.AUTHORIZATION);
if (StringUtils.isBlank(value) || !authService.isLogined(value)) {
throw new BizException(NacosServiceProviderReturnCode.NEED_LOGIN);
}
}
return joinPoint.proceed();
}
}
登录服务:我做了一个简易的鉴权逻辑,用于测试请求头
@Data
@ApiModel(value = "登录请求")
public class LoginRequest {
@ApiModelProperty(value = "用户名")
private String userName;
@ApiModelProperty(value = "密码")
@NotNull(message = "密码不能为空")
private String password;
}
@Data
@ApiModel(value = "登录响应")
public class LoginResponse {
@ApiModelProperty(value = "用户名")
private String uname;
@ApiModelProperty(value = "鉴权")
private String token;
}
@Service
public class AuthService {
private static String userName = "xxx";
private static String password = "xxx";
private static Map<String, String> tokenMap = new HashMap<>();
/**
* 测试功能,仅使用一个用户名和密码进行登录
*
* @param loginRequest
*/
public LoginResponse login(LoginRequest loginRequest) {
if (!loginRequest.getUserName().equals(userName)) {
throw new BizException(NacosServiceProviderReturnCode.LOGIN_USER_NOT_EXIST);
}
if (!loginRequest.getPassword().equals(password)) {
throw new BizException(NacosServiceProviderReturnCode.LOGIN_PASSWORD_FAIL);
}
LoginResponse response = new LoginResponse();
String token = UUID.randomUUID().toString().replaceAll("-", "");
tokenMap.put(token, userName);
response.setToken(token);
response.setUname(userName);
return response;
}
public boolean isLogined(String token) {
return tokenMap.containsKey(token);
}
}
接口:
@Slf4j
@Api(tags = "权限控制器")
@RestController
@RequestMapping(value = "/auth")
@RequiredArgsConstructor
public class AuthController {
private final AuthService authService;
@ApiOperation(value = "登录")
@PostMapping(value = "/login")
public JsonResult<LoginResponse> login(@RequestBody @Validated LoginRequest request) {
return JsonResult.success(authService.login(request));
}
}
通过JDK网络类Java.net.HttpURLConnection
比较原始的一种调用做法,这里把get请求和post请求都统一放在一个方法里面。
Post方式和Get方式使用步骤
(1)Post方式请求过程:
- 创建远程链接对象
- 设置请求方式(get,post,…)
- 设置连接超时时间
- 设置响应超时时间
- 发起连接
- 传入请求参数(可选)
- 获取请求响应
- 关闭连接
(2)Get方式请求过程:
- 拼接url参数,中文需要UrlEncode
- 创建远程链接对象
- 设置请求方式(get,post,…)
- 设置连接超时时间
- 设置响应读取时间
- 发起连接
- 获取请求响应
- 关闭连接
其实主要就是传递参数时不同
Post请求方式
测试程序:
根据步骤,新建doPost
方法:
这里的param
是已经被序列化后的JSON
public <T, R> R doPost(String httpUrl, T params, Class<R> returnType,String token) throws IOException {
StringBuilder result = new StringBuilder();
//连接
HttpURLConnection connection = null;
OutputStream os = null;
InputStream is = null;
BufferedReader br = null;
try {
//创建连接对象
URL url = new URL(httpUrl);
//创建连接
connection = (HttpURLConnection) url.openConnection();
//设置请求方法
connection.setRequestMethod("POST");
//设置连接超时时间(必须)
connection.setConnectTimeout(15000);
//设置读取超时时间(必须)
connection.setReadTimeout(15000);
//DoOutput设置是否向httpUrlConnection输出,DoInput设置是否从httpUrlConnection读入,此外发送post请求必须设置这两个
//设置是否可读取
connection.setDoOutput(true);
connection.setDoInput(true);
//设置通用的请求属性
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)");
connection.setRequestProperty("Content-Type", "application/json;charset=utf-8");
//(可选)设置请求头鉴权信息
if(StringUtils.isNotBlank(token)){
connection.addRequestProperty("Authorization",token);
}
// 建立实际的连接
connection.connect();
//拼装参数
if (null != params) {
String param = objectMapper.writeValueAsString(params);
//设置参数
os = connection.getOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(os, "UTF-8");
//拼装参数
writer.write(param);
writer.flush();
//另一种方式
//设置参数
//os = connection.getOutputStream();
//拼装参数
//os.write(param.getBytes("UTF-8"));
}
//设置权限
//设置请求头等
//开启连接
//connection.connect();
//读取响应
if (connection.getResponseCode() == 200) {
is = connection.getInputStream();
if (null != is) {
br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
String temp = null;
while (null != (temp = br.readLine())) {
result.append(temp);
result.append("\r\n");
}
}
}
} finally {
//关闭连接
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//关闭连接
connection.disconnect();
}
R object = objectMapper.readValue(result.toString(), returnType);
return object;
}
getToken
方法:
/**
* 获取第三方接口的token
*/
public String getToken() throws IOException {
String token = null;
Map<String, String> object = new HashMap<>();
object.put("userName", "xxx");
object.put("password", "xxx");
String url = "http://localhost:8082/nacos-service-provider/auth/login";
JsonResult jsonResult = doPost(url, object, JsonResult.class);
if (Objects.isNull(jsonResult) || !ReturnCode.SUCCESS.getCode().equals(jsonResult.getCode())) {
if (Objects.isNull(jsonResult)) {
throw new BizException(ReturnCode.ERROR);
} else {
throw new BizException(jsonResult.getCode(), jsonResult.getMessage());
}
}
LoginResponse loginResponse = objectMapper.convertValue(jsonResult.getData(), LoginResponse.class);
token = loginResponse.getToken();
return token;
}
Service
方法:
/**
* 使用HttpURLConnection发送Post请求
*/
@SneakyThrows
public TestHttpAccessResponse sendHttpRequestByJdk() {
TestHttpAccessRequest request = new TestHttpAccessRequest();
request.setAge(16);
request.setName("刘伞");
request.setAddress("佛山市");
String httpUrl = "http://localhost:8082/nacos-service-provider/testHttpAccess";
/**
* 如果是List<T>这种带泛型的对象,则需要使用TypeReference<类型> typeRef = new TypeReference...
* 注意日期的类型,需要事前设置类型转换器
*/
String token = httpUrlConnectionService.getToken();
JsonResult jsonResult = httpUrlConnectionService.doPost(httpUrl, request, JsonResult.class,token);
if (Objects.isNull(jsonResult) || !ReturnCode.SUCCESS.getCode().equals(jsonResult.getCode())) {
if (Objects.isNull(jsonResult)) {
throw new BizException(ReturnCode.ERROR);
} else {
throw new BizException(jsonResult.getCode(), jsonResult.getMessage());
}
}
/**
* 由于我做了统一的返回体JsonResult,所以还需要再转一遍
* 这里不封装进去是因为,除了JsonResult可能还会有其他的返回体
*/
TestHttpAccessResponse response = objectMapper.convertValue(jsonResult.getData(), TestHttpAccessResponse.class);
return response;
}
调试结果(软件:apifox):
无授权:
{
"code": "40026",
"message": "token不存在或过期,请登录",
"data": null
}
正常:
{
"code": "40000",
"message": "操作成功",
"data": {
"name": "刘伞",
"age": 16,
"address": "佛山市"
}
}
Get请求方式
虽然正常工作中不太会使用到Get请求,但是还是要了解一下
测试程序:
根据步骤,新建doGet
方法:
注意:
(1)注意这里不要把参数放到OutputStream
里面,否则会自动变成POST
请求
(2)String
参数都用UrlEncode
编码,否则有可能会提示Invalid Character
/**
* Http get请求
*
* @param httpUrl 连接
* @return 响应数据
*/
public String doGet(String httpUrl, String param,String token) {
//链接
HttpURLConnection connection = null;
InputStream is = null;
BufferedReader br = null;
StringBuffer result = new StringBuffer();
try {
//创建连接
if (StringUtils.isNotBlank(param)) {
httpUrl = httpUrl + "?" + param;
}
URL url = new URL(httpUrl);
connection = (HttpURLConnection) url.openConnection();
//设置请求方式
connection.setRequestMethod("GET");
//设置连接超时时间
connection.setReadTimeout(15000);
//(可选)设置请求头鉴权信息
if(StringUtils.isNotBlank(token)){
connection.addRequestProperty("Authorization",token);
}
//开始连接
connection.connect();
//请求参数(这里不要把参数放到outputStream中,会自动变成post请求)
/* if (StringUtils.isNotBlank(param)) {
OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream(), "UTF-8");
writer.write(param);
writer.flush();
}*/
//获取响应数据
if (connection.getResponseCode() == 200) {
//获取返回的数据
is = connection.getInputStream();
if (null != is) {
br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
String temp = null;
while (null != (temp = br.readLine())) {
result.append(temp);
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != br) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != is) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//关闭远程连接
connection.disconnect();
}
return result.toString();
}
测试方法:
/**
* 使用HttpURLConnection发送Post请求
*/
@SneakyThrows
public TestHttpAccessResponse sendHttpGetRequestByJdk() {
TestHttpAccessRequest request = new TestHttpAccessRequest();
request.setAge(16);
request.setName("刘伞");
request.setAddress("佛山市");
//拼接参数
String httpUrl = "http://localhost:8082/nacos-service-provider/testHttpAccessGet";
List<String> params = Lists.newArrayList();
Class<TestHttpAccessRequest> clazz = TestHttpAccessRequest.class;
Field[] declaredFields = clazz.getDeclaredFields();
for (Field declaredField : declaredFields) {
declaredField.setAccessible(true);
Object o = declaredField.get(request);
if (declaredField.getType().equals(String.class)) {
String s = (String) declaredField.get(request);
s = URLEncoder.encode(s);
o = s;
}
params.add(declaredField.getName() + "=" + o);
}
String param = params.stream().collect(Collectors.joining("&"));
//这里getToken方法沿用上面的
String token = httpUrlConnectionService.getToken();
String responseJson = httpUrlConnectionService.doGet(httpUrl, param,token);
/**
* 如果是List<T>这种带泛型的对象,则需要使用TypeReference<类型> typeRef = new TypeReference...
* 注意日期的类型,需要事前设置类型转换器
*/
JsonResult<Object> jsonResult = objectMapper.readValue(responseJson, JsonResult.class);
/**
* 由于我做了统一的返回体JsonResult,所以还需要再转一遍
*/
if (Objects.isNull(jsonResult) || !ReturnCode.SUCCESS.getCode().equals(jsonResult.getCode())) {
if (Objects.isNull(jsonResult)) {
throw new BizException(ReturnCode.ERROR);
} else {
throw new BizException(jsonResult.getCode(), jsonResult.getMessage());
}
}
TestHttpAccessResponse response = objectMapper.convertValue(jsonResult.getData(), TestHttpAccessResponse.class);
return response;
}
结果展示:
无授权:
{
"code": "40026",
"message": "token不存在或过期,请登录",
"data": null
}
正常:
{
"code": "40000",
"message": "操作成功",
"data": {
"name": "刘伞",
"age": 16,
"address": "佛山市"
}
}