项目信息通信 远程调用API
在A服务中 需要通过远程调用 B服务接口 获取数据。A项目与B项目部署在不同的服务器中。两个项目之间进行信息通信有很多中方式,比如有 Socket、Http、soap web service 、websocket、Rpc、mq、apache的HttpClient、java.net包下的HttpURLConnection、hutool.http.HttpRequest等等。
1、目标服务器的创建封装
- 在进行通信的前提条件是需要知道接口提供者的服务器IP地址和访问路径。首先模拟创建了一个接受请求的服务项目。分别提供了post和get两种类型的请求接口。
package cn.example.springbootproject.HttpClientPage.controller;
import cn.example.springbootproject.util.R;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
/**
* @Description: 向其他项目提供(Http协议)访问接口,其他项目进行接口的调用:客户端controller,提供项目接口调用的物理层
* @ClassName: serverController
*
* TODO -比如socket,http,soap web service,websocket,rpc等等
*
*/
@Controller
@RequestMapping("/httpClient")
public class ServerController {
//LoggerFactory.getLogger()
Logger logger = LoggerFactory.getLogger(ServerController.class);
@ResponseBody
@RequestMapping(value = "/testPost" ,method = RequestMethod.POST)
public JSONObject testPostRequest(@RequestBody String id){
logger.info("访问post类型数据接口! body参数id={}",id);
HashMap<Object, Object> result = new HashMap<>();
result.put("boolean",true);
result.put("Integer",110);
result.put("type","postMethod");
return R.ok("请求访问成功!",result);
}
@ResponseBody
@RequestMapping(value = "/testGet/{temp}",method = RequestMethod.GET)
public JSONObject testGetRequestMethod(
@PathVariable(value = "temp") String temp,
@RequestParam String id){
logger.info("访问get请求成功! 请求参数 temp={} id={}",temp,id);
HashMap<Object, Object> result = new HashMap<>();
result.put("boolean",true);
result.put("Integer",110);
result.put("type","getMethod");
return R.ok("访问Get请求成功!",result);
}
}
- util工具类下的统一返回值类
@Data
public class R extends JSONObject{
//调用是否成功
private Boolean sucess;
//返回码
private Integer code;
//返回消息
private String message;
//返回数据
private Map<String,Object> data = new HashMap<>();
private R(){} //构造方法私有化
//操作成功响应
public static R ok(String msg,Object resultObj){
R result = new R();
result.put("result","ok");
result.put("msg",msg);
result.put("resultObj",resultObj);
return result;
}
//操作失败响应
public static R error(String msg,Object resultObj){
R result = new R();
result.put("result","fail");
result.put("msg",msg);
result.put("resultObj",resultObj);
return result;
}
/**
*@Description 将JSon字符串转换为JSONObject
*/
public static R getJsonObjectInfo(String str){
/*
* JSONObject extends JSON(继承parseObject(String str) 方法)
* */
R jsonObject =(R) JSONObject.parseObject(str);
return jsonObject;
}
}
2、HttpURLConnection实现通信
HttpURLConnection是Java的标准类库,在JDK的java.net包中已经提供了访问Http协议的该类。HttpURLConnection继承自URLConnection,可以向指定网站发送GET请求,Post请求。它在URLConnection的基础上提供了一些便捷的方法。HttpURLConnection是一个抽象类,无法直接实例化对象。通过调用openConnection() 方法获得对象实例。
2.1 使用URL发送GET请求
首先需要通过URL.openConnection()来创建能够访问Http协议的HttpURLConnection类
通过HttpURLConnection.connect()来链接到目标服务器的接口,请求参数的封装需要放在请求连接之前完成。
然后会通过HttpURLConnection.getInputStream()来获取到响应数据,改响应数据是一个流对象,我们需要从流对象中读出响应数据,且注意该流对象是只能读取一次数据,读取第二次时会是空数据!
package cn.example.springbootproject.HttpUrlConnectonController_01;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* @Description: 通过调用java.net.HttpURLConnection 远程调用Get请求
* @ClassName: HttpURLConnectionTestGET
*/
public class HttpURLConnectionTestGET {
public static void main(String[] args) throws IOException {
//1.创建URL对象
URL url = new URL("http://localhost:8888/httpClient/testGet/restfulStyle?id=001");
//2.通过URL对象获取能够访问Http协议的类HttpURLConnection
HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
//3.封装请求相关参数
httpURLConnection.setRequestMethod("GET"); //请求方式
httpURLConnection.setRequestProperty("version", "1.0");//设置请求头
httpURLConnection.setConnectTimeout(6*1000); //设置超时时间
httpURLConnection.setReadTimeout(6*1000);
//4.建立连接
httpURLConnection.connect();
//5.得到响应数据(流对象)
InputStream inputStream = httpURLConnection.getInputStream();
byte[] buff = new byte[1024]; //创建缓存大小
int len = -1; //每次读取到的内容长度
/*获取io流中的数据方式1*/
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
/*获取io流中的数据方式2*/
StringBuffer result = new StringBuffer();
while ((len = inputStream.read(buff)) != -1){ //当没有内容可读取后为-1则推出
result.append(new String(buff,0,len));
byteArrayOutputStream.write(buff,0,len);
}
System.out.println("result = " + result);
String byteStream = byteArrayOutputStream.toString();
System.out.println("byteStream = " + byteStream);
//关闭流资源
byteArrayOutputStream.close();
inputStream.close();
httpURLConnection.disconnect();
}
}
/**
* result = {"result":"ok","msg":"访问Get请求成功!","resultObj":{"Integer":110,"boolean":true,"type":"getMethod"}}
* byteStream = {"result":"ok","msg":"访问Get请求成功!","resultObj":{"Integer":110,"boolean":true,"type":"getMethod"}}
* */
2.2 使用URL发送POST请求
httpURLConnection.setRequestProperty(“Content-Type”,“application/x-www-form-urlencoded”);
在使用HttpURLConnection发送POST请求时,传递参数的方式是建立连接之后,取出一个输出流,然后通过输出流将Body数据写到我们的请求之中。参数的格式是以键值对的方式,键值对之间用‘=’相连,多个键值对之间采用&来连接。
package cn.example.springbootproject.HttpUrlConnectonController_01;
//import javax.net.ssl.HttpsURLConnection;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* @Description: HttpURLConnection-测试发送POST请求
* @ClassName: HttpURLConnectionTestPOST
*/
public class HttpURLConnectionTestPOST {
public static void main(String[] args) throws Exception{
//1.创建URL
URL url = new URL("http://localhost:8888/httpClient/testPost/");
//2.创建HttpURLConnection对象
HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
//3.设置请求参数
httpURLConnection.setRequestMethod("POST"); //请求方式
httpURLConnection.setConnectTimeout(6*1000); //请求时间
httpURLConnection.setRequestProperty("version", "1.0");//设置请求头
httpURLConnection.setDoInput(true); //是否读入
httpURLConnection.setDoOutput(true); //是否读出
httpURLConnection.setUseCaches(false); //是否使用缓存
httpURLConnection.setInstanceFollowRedirects(true); // 设置此 HttpURLConnection 实例是否应该自动执行 HTTP 重定向
// 设置使用标准编码格式编码参数的名-值对
httpURLConnection.setRequestProperty("Content-Type","application/x-www-form-urlencoded");
httpURLConnection.connect(); //建立http连接
//4.通过输出流将参数写入请求之中
String parameters = "id=123456&name=纪录片";
OutputStream outputStream = httpURLConnection.getOutputStream(); //获取连接的输出流
outputStream.write(parameters.getBytes()); //将参数写入流中
outputStream.flush();
outputStream.close();
System.out.println("httpURLConnection.getResponseCode() = " + httpURLConnection.getResponseCode()); //获取响应状态码
System.out.println("httpURLConnection.getResponseMessage() = " + httpURLConnection.getResponseMessage()); //获取响应Message?
//5.从输入流中获取响应数据
InputStream inputStream = httpURLConnection.getInputStream();
String result = "";
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(inputStream));
String line;
while ((line = bufferedReader.readLine())!=null){
result += line;
}
System.out.println("响应结果:result = " + result);
//关闭流资源
inputStream.close();
bufferedReader.close();
httpURLConnection.disconnect(); //断开http连接
}
/**
* httpURLConnection.getResponseCode() = 200
* httpURLConnection.getResponseMessage() = null
* 响应结果:result = {"result":"ok","msg":"请求访问成功!","resultObj":{"Integer":110,"boolean":true,"type":"postMethod"}}
* */
}
connection.setRequestProperty(“Content-Type”, “application/json;charset=utf-8”);
设置编码格式Content-Type类型,实现传送JSON格式的参数到目标资源接口中
package cn.example.springbootproject.HttpUrlConnectonController_01;
//import javax.net.ssl.HttpsURLConnection;
import com.alibaba.fastjson.JSONObject;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
/**
* @Description: HttpURLConnection-测试发送POST请求
* @ClassName: HttpURLConnectionTestPOST
*/
public class HttpURLConnectionTestPOST {
public static void main(String[] args) throws Exception {
//1.创建URL
URL url = new URL("http://localhost:8888/httpClient/testPost/");
//2.创建HttpURLConnection对象
HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
//3.设置请求参数
httpURLConnection.setRequestMethod("POST"); //请求方式
httpURLConnection.setDoInput(true); //是否读入
httpURLConnection.setRequestProperty("version", "1.0");//设置请求头
httpURLConnection.setDoOutput(true); //是否读出
httpURLConnection.setUseCaches(false); //是否使用缓存
// 设置使用标准编码格式编码参数的名-值对
httpURLConnection.setRequestProperty("Content-Type","application/json;charset=utf-8"); //设置参数类型为JSON格式
httpURLConnection.connect(); //建立http连接
//4.通过输出流将参数写入请求之中
JSONObject jsonParams = new JSONObject();
jsonParams.put("id","1452");
jsonParams.put("name","战争片");
System.out.println("jsonParams.toJSONString() = " + jsonParams.toJSONString());
BufferedWriter bufferedWriter = //创建缓存字符输出流
new BufferedWriter(new OutputStreamWriter(httpURLConnection.getOutputStream()));
//写入body参数
bufferedWriter.write(jsonParams.toJSONString());
bufferedWriter.flush();
bufferedWriter.close();
//5.从输入流中获取响应数据
if (httpURLConnection.HTTP_OK == httpURLConnection.getResponseCode()){
//请求访问成功正常 200
InputStream inputStream = httpURLConnection.getInputStream();
String result = "";
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(inputStream));
String line;
while ((line = bufferedReader.readLine())!=null){
result += line;
}
System.out.println("响应结果:result = " + result);
//关闭流资源
inputStream.close();
bufferedReader.close();
httpURLConnection.disconnect(); //断开http连接
}
}
}
2.3 使用URL发送HTTPS请求
- 在java文件中实现
public class SslHandshakeExc_NsanMatchingIp{
public static void main(String[] args) throws Exception {
URL url = new URL("https://110.75.244.16/gateway.do"); // openapi.alipay.com
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
// 新增部分
conn.setHostnameVerifier(new SslHandshakeExc_NsanMatchingIp().new TrustAnyHostnameVerifier());
conn.connect();
InputStream is = conn.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
is.close();
}
// 定制Verifier
public class TrustAnyHostnameVerifier implements HostnameVerifier {
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
}
- 在jsp文件中的事先案例(老项目需要)
//1.创建URL对象
URL url = new URL(validateurl);
//2.通过URL对象获取能够访问Http协议的类HttpURLConnection
HttpsURLConnection HttpsURLConnection = (HttpsURLConnection) url.openConnection();
//3.封装请求相关参数
HttpsURLConnection.setRequestMethod("GET"); //请求方式
HttpsURLConnection.setRequestProperty("version", "1.0");//设置请求头
HttpsURLConnection.setConnectTimeout(6*1000); //设置超时时间
HttpsURLConnection.setReadTimeout(6*1000);
// 自定义 HostnameVerifier
HostnameVerifier allHostsValid = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true; // 不进行主机名验证
}
};
HttpsURLConnection.setHostnameVerifier(allHostsValid);
//4.建立连接
HttpsURLConnection.connect();
//5.得到响应数据(流对象)
InputStream inputStream = HttpsURLConnection.getInputStream();
byte[] buff = new byte[1024]; //创建缓存大小
int len = -1; //每次读取到的内容长度
/*获取io流中的数据方式1*/
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
/*获取io流中的数据方式2*/
StringBuffer result = new StringBuffer();
while ((len = inputStream.read(buff)) != -1){ //当没有内容可读取后为-1则推出
result.append(new String(buff,0,len));
byteArrayOutputStream.write(buff,0,len);
}
System.out.println("result = " + result);
byteStream = byteArrayOutputStream.toString();
System.out.println("byteStream = " + byteStream);
//关闭流资源
byteArrayOutputStream.close();
inputStream.close();
HttpsURLConnection.disconnect();
//String[] parts = byteStream.split(" ");
//username = parts[1];
//username2 = parts.toString();
Pattern pt = Pattern.compile("<cas:user>(.*)</cas:user>");
Matcher match = pt.matcher(byteStream);
username = match.group(0);
3、RestTemplate实现通信
spring框架提供的RestTemplate类可用于在应用中调用rest服务它简化了与http服务的通信方式,统一了Restful的标准,封装了http链接,我们只需要传入url及返回值类型即可。相较于之前常用的HttpClient,RestTemplate是一种更优雅的调用Restful服务的方式。
RestTemplate默认依赖JDK提供http连接的能力(HttpURLConnection),如果有需要的话也可以通过setRequestFactory方法替换为例如Apache HttpComponents、Netty或OkHttp等其它HTTP library。
spring并没有真正的去实现底层的http请求(3次握手),而是集成了别的http请求,spring只是在原有的各种http请求进行了规范标准,让开发者更加简单易用,底层默认用的是jdk的http请求。
方法组 | 说明 |
---|---|
getForObject | 通过 GET 检索一个表示结果。 |
getForEntity | 通过使用GET检索一个 ResponseEntity(即 status、header 和 body)。 |
headForHeaders | 通过使用 HEAD 检索一个资源的所有 header。 |
PostForLocation | 通过使用 POST 创建一个新的资源,并从响应中返回 Location header。 |
PostForObject | 通过使用POST创建一个新资源,并从响应中返回表示。 |
postForEntity | 通过使用POST创建一个新资源,并从响应中返回表示。 |
put | 通过使用PUT创建或更新一个资源。 |
patchForObject | 通过使用 PATCH 更新一个资源,并从响应中返回表示。请注意,JDK的 HttpURLConnection 不支持 PATCH,但 Apache 的 HttpComponents 和其他的支持。 |
delete | 通过使用 DELETE 删除指定URI上的资源。 |
optionsForAllow | 通过使用 ALLOW 为资源检索允许的HTTP方法。 |
exchange | 前面的方法的更通用(和更少的意见)版本,在需要时提供额外的灵活性。它接受一个 RequestEntity(包括HTTP方法、URL、header 和 body 作为输入)并返回一个 ResponseEntity。 |
execute | 执行请求的最通用方式,通过回调接口对请求准备和响应提取进行完全控制。 |
3.1 服务调用者环境准备
- 导入所需依赖
<!--RestTemplate实现远程调用-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
- 创建 RestTemplate 的配置类
package cn.example.springbootproject.HttpClientPage.clientPage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
/**
* @Description: RestTemplate配置类
*/
@Configuration
public class RestTemplateConfig {
//启动的时候要注意,因为我们在controller中注入了RestTemplate,所以在启动的时候需要实例化该类一个实例对象
@Autowired
private RestTemplateBuilder restTemplateBuilder;
//使用RestTemplateBuilder来实例化一个RestTemplate对象,spring中默认已经注入了RestTemplateBuilder实例
@Bean
public RestTemplate restTemplate(){
return restTemplateBuilder.build();
}
@Bean
public RestTemplate clientRestTemplate(){
HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
httpComponentsClientHttpRequestFactory.setConnectionRequestTimeout(3000);
httpComponentsClientHttpRequestFactory.setConnectTimeout(3000);
httpComponentsClientHttpRequestFactory.setReadTimeout(3000);
return new RestTemplate();
}
}
3.2 Send-POST/GETRequest
- HTTP+Post请求通过配置类中注册的RestTemplateBean就可以实现通信,调用目标服务端的接口了
package cn.example.springbootproject.HttpClientPage.RestTemplateClientController;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.client.RestTemplate;
@SpringBootTest
@RunWith(SpringRunner.class)
@Slf4j
public class ClientTestHttp {
@Autowired
private RestTemplate restTemplate;
@Test //测试利用RestTemplate发送Post请求(调用http接口!)
public void testPostMethod(){
String url = "http://localhost:8888/httpClient/testPost"; //接口提供者访问路径
JSONObject parameter = new JSONObject();
parameter.putIfAbsent("id",12);
//url:访问地址 parameter:post请求放入body中的参数 JSONObject.class:设置响应数据类型
JSONObject jsonObject = restTemplate.postForObject(url, parameter, JSONObject.class);
log.info("信息通信成功! result ={}",JSONObject.toJSONString(jsonObject));
}
@Test //测试RestTemplate发送GetRequest
public void testGetRequest(){
String url = "http://localhost:8888/httpClient/testGet/restfulStyle?id=001";
JSONObject getRequestResult = restTemplate.getForObject(url, JSONObject.class);
System.out.println("getRequestResult.toJSONString() = " + getRequestResult.toJSONString());
}
}
3.3 Send-HttpsRequest
- 如果需要通过HTTPS的请求方式进行通信,则还需要在原来的基础上封装一个Https请求工厂类。
package cn.example.springbootproject.HttpClientPage.RestTemplateClientController;
/*import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.ssl.SSLContexts;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import java.net.HttpURLConnection;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;*/
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.conn.ssl.TrustStrategy;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import java.net.HttpURLConnection;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
/**
* @Description: HttpsRequestFactory : Https请求工厂类
*/
public class HttpsClientRequestFactory extends SimpleClientHttpRequestFactory {
@Override
protected void prepareConnection(HttpURLConnection connection,String httpMethod){
try {
if (!(connection instanceof HttpsURLConnection)){ //http协议
// throw new RuntimeException("An instance of HttpsURLConnection is expected");
super.prepareConnection(connection,httpMethod);
}
if (connection instanceof HttpsURLConnection){ //https协议,修改版本协议
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
//信任任何链接
TrustStrategy anyTrustStrategy = new TrustSelfSignedStrategy/*TrustStrategy*/(){
@Override
public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
return true;
}
};
SSLContext ctx = SSLContexts.custom().useTLS().loadTrustMaterial(trustStore, anyTrustStrategy).build();
((HttpsURLConnection)connection).setSSLSocketFactory(ctx.getSocketFactory());
HttpsURLConnection httpsConnection = (HttpsURLConnection)connection;
super.prepareConnection(httpsConnection, httpMethod);
}
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
- 采用Https的方式调用目标服务器的资源接口:
*注:前提条件服务端接口支持Https:
- 申请一个 SSL 证书,并将其安装到服务器上。
- 修改服务器配置,使其支持 HTTPS。这可能需要在服务器上安装特定的软件和模块。
- 在接口代码中,使用 HTTPS 协议进行请求。
- 检查接口是否能正常工作并返回正确的结果。
package cn.example.springbootproject.HttpClientPage.RestTemplateClientController;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.client.RestTemplate;
@SpringBootTest
@RunWith(SpringRunner.class)
@Slf4j
public class ClientTestHttp {
@Autowired
private RestTemplate restTemplate;
@Test //测试Https请求!
public void TestHttpsRequest(){
RestTemplate restTemplate = new RestTemplate(new HttpsClientRequestFactory());
String url = "https://localhost:8888/httpClient/testPost";
ResponseEntity<JSONObject> result = restTemplate.getForEntity(url, JSONObject.class);
System.out.println("JSON.toJSONString(result) = " + JSON.toJSONString(result));
}
}
3.4 RestTemplate请求参数的封装
Entity未RestTemplate的实体类,内部包含请求头/响应头、请求体/响应体、状态码 等
ResponseEntity / RequstEntity
/**
* url: 请求地址;
* method: 请求类型(如:POST,PUT,DELETE,GET);
* requestEntity: 请求实体,封装请求头,请求内容
* responseType: 响应类型,根据服务接口的返回类型决定
* uriVariables: url中参数变量值
*/
package cn.example.springbootproject.HttpClientPage.RestTemplateClientController;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.annotation.JsonAlias;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.HttpHead;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.*;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.security.PublicKey;
/**
* @Description: 客户端访问调用服务端接口
* @ClassName: ClientController
* problem: springboot项目没有resource和开始
*/
@SpringBootTest
@RunWith(SpringRunner.class)
@Slf4j
public class ClientTestHttp {
@Autowired
private RestTemplate restTemplate;
@Test //普通接口的调用
public void testRestTemplateRequest(){
//资源访问地址
String url = "http://localhost:8888/httpClient/testPost";
//参数的封装
JSONObject parameter = new JSONObject();
parameter.put("id",1024);
/* 该方式不行!!
//请求头的封装
HttpHead httpHead = new HttpHead();
httpHead.addHeader("RestTemplateRequestType","Post");
//参数封装到HttpEntity实体类中 第一个参数:body,第二个参数:headers
HttpEntity<String> stringHttpEntity = new HttpEntity<>(parameter.toJSONString(), (MultiValueMap<String, String>) httpHead); */
RequestEntity<JSONObject> requestEntity = RequestEntity
.post(url)
.header(HttpHeaders.COOKIE,"xxxxxx") //添加token信息
.header("xx","xx") //添加请求头
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.body(parameter);
ResponseEntity<JSONObject> responseEntity =
restTemplate.exchange(url, HttpMethod.POST,requestEntity, JSONObject.class);
//解析响应数据:
HttpStatus statusCode = responseEntity.getStatusCode(); //响应状态码
System.out.println("statusCode = " + statusCode);
HttpHeaders headers = responseEntity.getHeaders(); //响应头数据
System.out.println("headers = " + headers);
int statusCodeValue = responseEntity.getStatusCodeValue();
System.out.println("statusCodeValue = " + statusCodeValue);
JSONObject body = responseEntity.getBody(); //响应体
System.out.println("body.toJSONString() = " + body.toJSONString());
}
}
/** 控制台输出
statusCode = 200 OK
headers = [Content-Type:"application/json", Transfer-Encoding:"chunked", Date:"Tue, 12 Sep 2023 02:54:07 GMT", Keep-Alive:"timeout=60", Connection:"keep-alive"]
statusCodeValue = 200
body.toJSONString() = {"result":"ok","msg":"请求访问成功!","resultObj":{"Integer":110,"boolean":true,"type":"postMethod"}}
*/
*标准方式设置参数:
private void getAccessToken() {
try {
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "client_credentials");
params.add("client_id", clientId);
params.add("client_secret", clientSecret);
RestTemplate client = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
ResponseEntity<JSONObject> response = client.exchange(uaaUrl, HttpMethod.POST, new HttpEntity<>(params, headers), JSONObject.class);
log.info("获取 Access Token 请求地址 {}请求参数{}请求响应{}", uaaUrl, params, response.getBody());
accessToken = Objects.requireNonNull(response.getBody()).getString("access_token");
accessTokenIsExist = true;
} catch (Exception e) {
log.error("getAccessToken 发生错误!错误信息:{}", e.getMessage());
accessTokenIsExist = false;
}
}
3.5 RestTemplate实现文件上传
- 首先服务端创建了一个文件上传的接口
@RequestMapping(value = "/FileupLoad",method = RequestMethod.POST)
@ResponseBody
public JSONObject fileUploadTest(@RequestParam("file")MultipartFile[] files, HttpServletRequest request) throws IOException {
//携带其他参数可以通过getParameter来获取
String parameter2 = request.getParameter("parameter2");
if (files == null){
return R.error(" 文件上传错误,服务器未拿到上传的文件!",null);
}
for (MultipartFile file : files){
if (!file.isEmpty() && file.getSize()>0) {
String filename = file.getOriginalFilename();
file.transferTo(new File("E:\\program_Test\\"+filename));
}
}
return R.ok("文件上传成功!",null);
}
- RestTemplate文件上传接口调用的参数封装
@Test //测试文件上传!
public void testFileUpLoad(){
String url = "http://localhost:8888/httpClient/fileUpLoad";
//设置请求头
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
//设置请求体 注意是 LinkedMultiValueMap
FileSystemResource resource = new FileSystemResource("C:\\Users\\AD\\Desktop\\图片测试.jpg");
LinkedMultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
form.add("file",resource);
HttpEntity<LinkedMultiValueMap<String,Object>> file = new HttpEntity<>(form,httpHeaders);
JSONObject responseResult = restTemplate.postForObject(url, file, JSONObject.class);
System.out.println("responseResult.toJSONString() = " + responseResult.toJSONString());
}
4、WebClient实现通信
WebClient是从Spring WebFlux 5.0版本开始提供的一个非阻塞的基于响应式编程的进行Http请求的客户端工具。它的响应式编程的基于Reactor的。WebClient中提供了标准Http请求方式对应的get、post、put、delete等方法,可以用来发起相应的请求。
WebClient 与HttpClient 的区别:
- WebClient在SpringBoot是有启动器的,HttpClient没有启动器,需要自己去配置
- WebClent简单,HttpClient功能更强大,看需选择,HttpClient用的人相对会比较多
使用场景:当我需要一个第三方接口返回数据时,我们不通过网关或者oapi等其它方式去调用,而是直接本地调其它服务的方式获取返回数据,此时用到这两个类了。其实就连SpringCloud中服务和服务之间的调用全部是使用HttpClient
- xml文件中导入WebClient相关依赖
<!-- WebClient实现远程调用 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
- 利用 WebClient 发送Get请求
package cn.example.springbootproject.WebClientController_03;
import com.alibaba.fastjson.JSONObject;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
/**
* @Description: 使用WebClient请求远程调用服务
* @ClassName: WebClientRequestTest
*/
public class WebClientRequestTest {
public static void main(String[] args) {
WebClient webClient = WebClient.create();
Mono<JSONObject> jsonObjectMono = webClient
.get() //get请求方式访问
.uri("http://localhost:8888/httpClient/testGet/restfulStyle?id=001") //请求资源地址
.header("testHead", "123456") //请求头的封装
.cookie("key1", "test1") //Cookie封装
.retrieve().bodyToMono(JSONObject.class); //响应结果类型设置
//获取响应数据
JSONObject block = jsonObjectMono.block();
System.out.println("block.toJSONString() = " + block.toJSONString());
}
}
- 利用WebClient发送POST请求
package cn.example.springbootproject.WebClientController_03;
import com.alibaba.fastjson.JSONObject;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.util.MultiValueMap;
import org.springframework.util.MultiValueMapAdapter;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
/**
* @Description: 使用WebClient请求远程调用服务
* @ClassName: WebClientRequestTest
*/
public class WebClientRequestTest {
public static void main(String[] args) {
//1.创建WebClient对象
WebClient webClient = WebClient.create();
//2.封装POST请问体body
/***
* TODO--参数封装存在问题!!!
* */
HashMap<String, List<String>> bodyMap1 = new HashMap<>();
bodyMap1.put("id", Arrays.asList("123"));
bodyMap1.put("name", Arrays.asList("古装剧!"));
MultiValueMap<String, String> body = new MultiValueMapAdapter<>(bodyMap1);
//参数封装方式2
JSONObject jsonBody = new JSONObject();
jsonBody.put("id","1314");
jsonBody.put("name","springboot");
//body封装方式3
String srtBody = jsonBody.toJSONString();
//3.封装请求相关数据
WebClient.ResponseSpec retrieve = webClient
.method(HttpMethod.POST) //设置请求方式 也可以直接采用.post()
//.uri(url, params) // 这里的params是拼接在url中的,可以用@PathVariable接收的参数
.uri("http://localhost:8888/httpClient/testPost")
.accept(MediaType.APPLICATION_JSON) // contentType
.contentType(MediaType.APPLICATION_JSON) // contentType
.header("testHeadInfo", "test1")
.cookie("cookieTest", "test02")
// 这里可以放入BodyInserters.from……多种方法,按需使用
//.body(Mono.just(jsonBody),JSONObject.class)
//.body(BodyInserters.fromFormData(body)) // 构造的参数可以用@RequestParam、@RequestBody接收
.bodyValue(srtBody)
.retrieve();// 发送请求
//4.获取响应数据
JSONObject result = retrieve.bodyToMono(JSONObject.class).block();
System.out.println("result.toJSONString() = " + result.toJSONString());
}
/*
* result.toJSONString() = {"result":"ok","msg":"请求访问成功!","resultObj":{"Integer":110,"boolean":true,"type":"postMethod"}}
* */
}
5、HttpClient实现通信
HttpClient是Apache的客户端的http通信实现库,这个类库的作用是接收和发送http报文,使用这个类库,它相比传统的 HttpURLConnection,增加了易用性和灵活性,我们对于http的操作会变得简单一些;SpringCloud中服务和服务之间的调用全部是使用HttpClient,HttpClient是非常强大的。
- pom.xml中导入相关依赖
<!-- HttpClient实现远程调用 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
- 调用已知服务ip和请求接口,利用HttpClient发送POST请求DEMO
package cn.example.springbootproject.HttpClientController_04;
import com.alibaba.fastjson.JSONObject;
import net.minidev.json.JSONUtil;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
/**
* @Description: HttpClient实现资源的远程调用测试
* @ClassName: HttpClientRequest
*/
public class HttpClientRequest {
public static void main(String[] args) throws IOException {
//1.创建HttpClient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
//2.请求数据的处理
HttpPost httpPost = new HttpPost("http://localhost:8888/httpClient/testPost"); //Post请求创建
httpPost.setHeader("Content-type","application/json;charset=UTF-8"); //请求格式
/**
* body参数的封装
* TODO--访问post类型数据接口! body参数id={"name":"??HttpClient","id":"123456"},name=null: 参数封装还是存在问题待处理!!!!
* */
JSONObject jsonBody = new JSONObject();
jsonBody.put("id","123456");
jsonBody.put("name","测试HttpClient");
StringEntity stringEntity = new StringEntity((String) jsonBody.toJSONString()); //String类型的JSON数据
stringEntity.setContentType("text/json");
httpPost.setEntity(stringEntity);
/*
* 3.设置超时-- 连接超时:setConnectTimeout;传输超时:setSocketTimeout
* */
RequestConfig build = RequestConfig.custom().setConnectTimeout(3 * 1000).setSocketTimeout(3 * 1000).build();
httpPost.setConfig(build);
//4.执行PostRequst
CloseableHttpResponse executeResult = httpClient.execute(httpPost);
//5.处理响应结果
//获取响应状态码
System.out.println("executeResult.getStatusLine().getStatusCode() = " + executeResult.getStatusLine().getStatusCode());
//获取响应头信息
//获取响应头信息
Iterator<Header> iterator = Arrays.stream(executeResult.getAllHeaders()).iterator();
while (iterator.hasNext()){
Header next = iterator.next();
System.out.println(next.getName()+"=="+next.getValue());
}
//获取响应数据
String result = EntityUtils.toString(executeResult.getEntity(), "UTF-8");
System.out.println("result = " + result);
}
}
- 利用HttpClient调用GETREquest示例
package cn.example.springbootproject.HttpClientController_04;
import com.alibaba.fastjson.JSONObject;
import net.minidev.json.JSONUtil;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Iterator;
/**
* @Description: HttpClient实现资源的远程调用测试
* @ClassName: HttpClientRequest
*/
public class HttpClientRequest {
public static void main(String[] args) throws IOException {
//1.创建HttpClient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
//2.封装GET请求
HttpGet httpGet = new HttpGet("http://localhost:8888/httpClient/testGet/restfulStyle?id=001");
httpGet.setHeader("test","001");
httpGet.setHeader("Content-Type","application/json;charset=UTF-8");
httpGet.setConfig(RequestConfig.custom().setConnectTimeout(6*10000).setSocketTimeout(6*10000).build());
//3.执行GETRequest
CloseableHttpResponse execute = httpClient.execute(httpGet);
String result = EntityUtils.toString(execute.getEntity(), "UTF-8");
System.out.println("result = " + result);
}
}
6、Hutool-HttpRequest 实现通信
Hutool-http针对JDK的HttpUrlConnection做一层封装,简化了HTTPS请求、文件上传、Cookie记忆等操作,使Http请求变得无比简单。其核心类为HttpRequest和HttpResponse。与此同时,Hutool-http针对一些场景封装了HttpUtil工具类,让一些操作变得更简单。
优点:
1.根据URL自动判断是请求HTTP还是HTTPS,不需要单独写多余的代码。
2.表单数据中有File对象时自动转为multipart/form-data表单,不必单做做操作。
3.默认情况下Cookie自动记录,比如可以实现模拟登录,即第一次访问登录URL后后续请求就是登录状态。
4.自动识别304跳转并二次请求
5.自动识别页面编码,即根据header信息或者页面中的相关标签信息自动识别编码,最大可能避免乱码。
6.自动识别并解压Gzip格式返回内容
- pom.xml中导入相关依赖
<!-- hutool-HttpRequest 实现Http远程调用-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.8</version>
</dependency>
- Hutool-HttpRequest–Post请求例子
package cn.example.springbootproject.HutoolHttpReuestClientController_05;
import cn.hutool.http.Header;
import cn.hutool.http.HttpRequest;
import com.alibaba.fastjson.JSONObject;
/**
* @Description: Hutool工具类封装的HttpRequest测试
* @ClassName: HuToolHttpRequest
*/
public class HuToolHttpRequest {
//testPostRequest
public static void main(String[] args) {
//封装body参数
JSONObject jsonBody = new JSONObject();
jsonBody.put("id","122345");
jsonBody.put("name","后端工程师");
//链式构建请求
String hutoolHttpResult = HttpRequest
.post("http://localhost:8888/httpClient/testPost")
.header(Header.USER_AGENT, "Hutool http") //头信息,多个头信息多次调用此方法即
//.form() //表单内容
.timeout(6 * 1000) //超时时间
.body(jsonBody.toJSONString()) //body参数
.execute().body();
System.out.println("hutoolHttpResult = " + hutoolHttpResult);
/*
* hutoolHttpResult = {"result":"ok","msg":"请求访问成功!","resultObj":{"Integer":110,"boolean":true,"type":"postMethod"}}
* */
}
}
- Hutool-HttpReqquest-GET请求例子
package cn.example.springbootproject.HutoolHttpReuestClientController_05;
import cn.hutool.http.Header;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import java.net.HttpCookie;
import java.util.Iterator;
import java.util.List;
/**
* @Description: Hutool工具类封装的HttpRequest测试
* @ClassName: HuToolHttpRequest
*/
public class HuToolHttpRequest {
//GETRequestTest
public static void main(String[] args) {
//1.GETRequest设置
HttpResponse result = HttpRequest
.get("http://localhost:8888/httpClient/testGet/restfulStyle?id=001")
.header("Content-Type", "application/json;charset=UTF-8")
.cookie("stet")
.timeout(6 * 1000)
.execute();
//2.响应数据处理
//cookie数据
List<HttpCookie> cookies = result.getCookies();
Iterator<HttpCookie> iterator = cookies.iterator();
while (iterator.hasNext()){
HttpCookie next = iterator.next();
System.out.print("CookieInfo: "+next.getName()+"=");
System.out.println(next.getValue());
}
//响应状态码
System.out.println("result.getStatus() = " + result.getStatus());
//响应体
System.out.println("result.body() = " + result.body());
}
}
7、其他笔记
7.1 请求头中的数据
Accept : 浏览器能够接收服务器响应的数据类型。
Accept-Encoding:浏览器能够接收服务器响应的数据的压缩格式。
Accept-Language: 浏览器能够接收的语言类型。
Host: 当前请求访问的目标地址。
Cookie:当前请求需要携带的cookie信息。
8、补充更新总结
补充内容参考文章:https://mp.weixin.qq.com/s/2G87_t_mbEN7ssKxCXIFZQ
8.1 使用FeignClient调用
Feign是一款轻量级的HTTP服务客户端远程调用框架,相比较于传统上的HTTP封装接口,它以Java接⼝注解的⽅式调⽤HTTP请求,使服务间的调用变的简单。
- 1.引入依赖
<!-- Feign注解:注意版本要与springBoot匹配 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<!-- <version>4.0.1</version> -->
</dependency>
- 2.在启动类上加上@EnableFeignClients注解
- 3.Feign客户端代码
@FeignClient(name = "masterdata",url = "${masterdata-service-url}")
public interface ISysUserClient {
@GetMapping(value = "/masterdata/getSysUserById")
public Map getSysUserById(@RequestParam("userId") String userId);
}
@FeignClient注解的常用属性说明
name:指定Feign的名称,如果使用了注册中心,name属性会作为微服务的名称,用于服务发现
url:Feign调用的跳转路径,可以在配置文件中设置,多用于代码调试
8.2 使用RestTemplate调用
RestTemplate中几个常用的方法:getForObject()、getForEntity()、postForObject()、postForEntity()。其中,getForObject() 和 getForEntity() 方法可以用来发送 GET 请求。
-
1.引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
虽然SpringBoot会自动的装配很多常见的bean,但是RestTemplate,我们需要显示的配置它。
-
2.RestTemplateConfig配置类
@Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate(ClientHttpRequestFactory factory){ return new RestTemplate(factory); } @Bean public ClientHttpRequestFactory simpleClientHttpRequestFactory(){ SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); factory.setReadTimeout(5000);//单位为ms factory.setConnectTimeout(5000);//单位为ms return factory; } }
SimpleClientHttpRequestFactory类对应的HTTP库是JDK自带的HttpUrlConnection,当然我们可以根据自身的需求使用其他的HTTP库,例如HttpComponentsAsyncClientHttpRequestFactory。
-
3.客户端调用接口代码
我们还可以通过HttpHeader增加一些请求头的属性,例如请求头格式,或者一些需要用户认证的信息。
@RestController public class TestRestTemplate { @Resource private RestTemplate restTemplate; @GetMapping(value = "/saveUser") public void saveUser(String userId) { String url = "http://127.0.0.1:8094/masterdata/sysUser/saveUser"; Map map = new HashMap<>(); map.put("name", "郭郭"); HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.setContentType(MediaType.APPLICATION_JSON); HttpEntity httpEntity = new HttpEntity(map, httpHeaders); String results = restTemplate.postForObject(url, httpEntity, String.class); } }
postForObject方法中的第三个属性String.class是响应数据的类型,这个要根据自身代码的接口,正确书写,否则会报错。
8.3 使用WebClient调用
Spring3.0引入了RestTemplate,但是在后来的官方源码中介绍,RestTemplate有可能在未来的版本中被弃用,所谓替代RestTemplate,在Spring5中引入了WebClient作为异步的非阻塞、响应式的HTTP客户端。
-
1.引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
-
2.WebClient示例代码
public class TestWebClient {
@Test
public void doGet() {
String userId = "郭郭";
String url = "http://127.0.0.1:8094/masterdata/sysUser/getSysUserById?userId={userId}";
Mono<String> mono = WebClient
//创建WebClient实例
.create()
//方法调用,WebClient中提供了多种方法
.get()
//请求url
.uri(url, userId)
//获取响应结果
.retrieve()
//将结果转换为指定类型
.bodyToMono(String.class);
//返回最终结果:block是阻塞的/subscribe()非阻塞式获取响应结果
System.out.println("响应结果:" + mono.block());
}
@Test
public void doPost() {
Map map = new HashMap<>();
map.put("name", "郭郭");
String requestBody = JSON.toJSONString(map);
String url = "http://127.0.0.1:8094/masterdata/sysUser/saveUser";
Mono<String> mono = WebClient
//创建WebClient实例
.create()
//方法调用,WebClient中提供了多种方法
.post()
//请求url
.uri(url)
//指定请求的Content-Type为JSON
.contentType(MediaType.APPLICATION_JSON)
//使用bodyValue方法传递请求体
.bodyValue(requestBody)
//获取响应结果
.retrieve()
//将结果转换为指定类型
.bodyToMono(String.class);
//返回最终结果:block是阻塞的/subscribe()非阻塞式获取响应结果
System.out.println("响应结果:" + mono.block());
}
}
在上述doPost请求中,我们的请求接口入参是一个Map,但是需要转换为JSON格式传递,这是因为WebClient默认是使用JSON序列化的。
8.4 使用Apache HttpClient调用
Apache HttpClient 是 Apache 提供的一个同步的 HTTP 客户端库,它提供了丰富的 API,可以很方便地发送 HTTP 请求和获取 HTTP 响应。
-
1.引入依赖
<!-- HttpClient实现远程调用 --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpcore</artifactId> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> </dependency>
-
2.示例代码
public class TestHttpClient {
@Test
public void doGet() throws IOException {
//步骤一:创建httpClient实例
CloseableHttpClient httpClient = HttpClients.createDefault();
//步骤二:创建HTTP请求
HttpGet httpGet = new HttpGet("http://127.0.0.1:8094/masterdata/sysUser/getSysUserById?userId=郭郭");
//步骤三:发送请求并获取响应数据
CloseableHttpResponse response = httpClient.execute(httpGet);
//步骤四:处理响应数据
HttpEntity entity = response.getEntity();
String result = EntityUtils.toString(entity);
//步骤五:关闭httpClient和response
response.close();
httpClient.close();
}
@Test
public void doPost() throws IOException {
//步骤一:创建httpClient实例
CloseableHttpClient httpClient = HttpClients.createDefault();
//步骤二:创建HTTP请求
HttpPost httpPost = new HttpPost("http://127.0.0.1:8094/masterdata/sysUser/saveUser");
//步骤三:设置请求体数据,使用JSON格式
Map map = new HashMap<>();
map.put("name", "郭郭");
String requestBody = JSON.toJSONString(map);
StringEntity stringEntity = new StringEntity(requestBody, "UTF-8");
stringEntity.setContentType("application/json");
httpPost.setEntity(stringEntity);
//步骤四:发送请求并获取响应数据
CloseableHttpResponse response = httpClient.execute(httpPost);
//步骤五:处理响应数据
HttpEntity entity = response.getEntity();
String result = EntityUtils.toString(entity);
//步骤五:关闭httpClient和response
response.close();
httpClient.close();
}
}
上述doPost方法中,我们使用了map转String,使用了JSON,这里引入JSON包即可。
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.25</version>
</dependency>
8.5 使用HttpURLConnection调用
HttpURLConnection 是 Java 自带的一个 HTTP 客户端工具,可以发送 HTTP 请求和接收 HTTP 响应。
-
1.示例代码
public class TestHttpURLConnection { @Test public void doGet() throws IOException { String userId = "郭郭"; // 参数值 userId = URLEncoder.encode(userId, "UTF-8"); // 对参数值进行URL编码 //步骤一:创建URL对象 URL url = new URL("http://127.0.0.1:8094/masterdata/sysUser/getSysUserById?userId=" + userId); //步骤二:打开连接 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); //步骤三:设置请求方式 conn.setRequestMethod("GET"); //步骤四:读取响应内容 BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); StringBuilder sb = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { sb.append(line); } reader.close(); System.out.println(sb.toString()); } @Test public void doPost() throws IOException { //创建URL对象 URL url = new URL("http://127.0.0.1:8094/masterdata/sysUser/saveUser"); //打开连接 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); //设置请求方式 conn.setRequestMethod("POST"); // 设置请求头 conn.setRequestProperty("Content-Type", "application/json"); //启用输出流 conn.setDoOutput(true); //设置请求体数据 Map map = new HashMap<>(); map.put("name", "郭郭"); String requestBody = JSON.toJSONString(map); //发送请求体数据 try (DataOutputStream outputStream = new DataOutputStream(conn.getOutputStream())) { outputStream.write(requestBody.getBytes(StandardCharsets.UTF_8)); } //读取响应内容 BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); StringBuilder sb = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { sb.append(line); } reader.close(); System.out.println(sb.toString()); } }
doPost请求比doGet请求多了一些逻辑,比如我们设置了conn.setDoOutPut,这用于启动输出流。
当您发送HTTP POST请求时,通常需要将数据发送到服务器,这些数据包含在请求体中。通过调用 setDoOutput(true),您告诉连接对象您将使用输出流来发送数据,这样它会准备好接受输出流,并将数据发送到服务器。这是发送POST请求体数据所必需的步骤。
另一方面,对于HTTP GET请求,通常不需要发送请求体数据,因此不需要设置 setDoOutput(true)。
而post请求的入参,我们是放入DataOutputStream进行传递,这里我们使用try语句块来包裹DataOutputStream,是因为DataOutputStream实现了AutoCloseable接口,因此它会在try块结束的时候自动关闭。
我们介绍了HttpURLConnection,还有一种调用方式是URLConnection,它们是什么关系呢?
通过查看源码,我们不难发现,HttpURLConnection继承自URLConnection,是它的一个子类,而HttpURLConnection专门用于处理HTTP协议的连接,如果需要处理其他协议,我们可以考虑使用通用的URLConnection。
8.6 使用 OKHttp 调用
OkHttp是一款高效的HTTP客户端框架,经过优化,具有低内存占有和出色的性能。
- 1.引入依赖
<!--okhttp依赖-->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.0.0</version>
</dependency>
- 2.示例代码
public class TestOkHttp {
@Test
public void doGet() throws IOException {
OkHttpClient client = new OkHttpClient();
String url = "http://127.0.0.1:8094/masterdata/sysUser/getSysUserById?userId=郭郭";
Request request = new Request.Builder().url(url).build();
try (Response response = client.newCall(request).execute()) {
ResponseBody body = response.body();
System.out.println(body.string());
}
}
@Test
public void doPost() throws IOException{
OkHttpClient client = new OkHttpClient();
String url = "http://127.0.0.1:8094/masterdata/sysUser/saveUser";
MediaType mediaType = MediaType.get("application/json; charset=utf-8");
//requestBody请求入参
Map map = new HashMap<>();
map.put("name", "郭郭");
RequestBody requestBody = RequestBody.create(mediaType, JSON.toJSONString(map));
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
try (Response response = client.newCall(request).execute()) {
ResponseBody body = response.body();
System.out.println(body.string());
}
}
}
doGet方法中,我们没有指定请求方式,因为Request默认就是get请求,所以构造的时候,可以省略。
8.7 使用 AsyncHttpClient调用
AsyncHttpClient是一个支持异步HTTP请求的开源库,用于非阻塞I/O操作,适用于需要高并发,非阻塞的应用。
- 1.引入依赖
<dependency>
<groupId>org.asynchttpclient</groupId>
<artifactId>async-http-client</artifactId>
<version>2.12.3</version>
</dependency>
- 2.示例代码
public class TestAsyncHttpClient {
@Test
public void doGet() throws IOException {
try (AsyncHttpClient client = new DefaultAsyncHttpClient();) {
BoundRequestBuilder requestBuilder = client.prepareGet("http://127.0.0.1:8094/masterdata/sysUser/getSysUserById?userId=郭郭");
CompletableFuture<String> future = requestBuilder.execute()
.toCompletableFuture()
.thenApply(Response::getResponseBody);
//使用join等待响应完成
String responseBody = future.join();
System.out.println(responseBody);
}
}
@Test
public void doPost() throws IOException {
try (AsyncHttpClient client = new DefaultAsyncHttpClient();) {
BoundRequestBuilder requestBuilder = client.preparePost("http://127.0.0.1:8094/masterdata/sysUser/saveUser");
//requestBody请求入参
Map map = new HashMap<>();
map.put("name", "郭郭");
String requestBody = JSON.toJSONString(map);
requestBuilder.addHeader("Content-Type", "application/json");
requestBuilder.setBody(requestBody);
CompletableFuture<String> future = requestBuilder.execute()
.toCompletableFuture()
.thenApply(Response::getResponseBody);
//使用join等待响应完成
String responseBody = future.join();
System.out.println(responseBody);
}
}
}
我们看到doGet、doPost方法都使用了try代码块,对AsyncHttpClient进行包裹,同理因为继承了AutoCloseable,为了自动调用close关闭功能。
我们在上述文章中,提到过的WebClient,也是支持异步,适用于高并发,非阻塞的场景,那么它们二者有什么区别呢?
通过上网查阅资料,得知,AsyncHttpClient是一个独立的开源库,它不依赖于任何的框架或者技术栈,而WebClient是Spring Framework的一部分,如果你正在构建Spring应用程序,那么WebClient可能是更好的选择。
8、补充更新总结
8.1 HttpURLConnection Https不进行主机名验证设置
- 报错信息:
javax.net.ssl.SSLHandshakeException:
Caused by: java.security.cert.CertificateException: No subject alternative names matching IP address 110.75.244.16 found
at sun.security.util.HostnameChecker.matchIP(Unknown Source)
at sun.security.util.HostnameChecker.match(Unknown Source)
at sun.security.ssl.X509TrustManagerImpl.checkIdentity(Unknown Source)
at sun.security.ssl.X509TrustManagerImpl.checkIdentity(Unknown Source)
at sun.security.ssl.X509TrustManagerImpl.checkTrusted(Unknown Source)
at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(Unknown Source)
... 12 more
在创建 SSL 连接时,HttpsClient 步骤并进行基本的服务器身份验证,以防止 URL 欺骗,其中包括验证服务器的名称是否在证书中HttpsClient 主要使用 HostNameChecker 检查主机名和证书中指定的名称。如果失败了,HostNameVerifier 就会出现,它被用来验证主机名。 在 HostNameVerifier 没有被重写时,默认是这个验证是错误的,也就意味着 HostNameChecker 失败后就会抛出这个异常 HostNameChecker 在实现上,如果传入的主机名是 IP 地址,将由 matchIP 方法在可用的条目中搜索IP地址对应的名称,同时在没有条目可以提供和IP地址匹配的名称时抛出 CertificateException 异常 所以,如果想通过使用 IP 作为主机名连接,证书中应该包含名称和与其匹配的 IP 地址这个条目
- Java中实现不进行主机名验证
public class SslHandshakeExc_NsanMatchingIp{
public static void main(String[] args) throws Exception {
URL url = new URL("https://110.75.244.16/gateway.do"); // openapi.alipay.com
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
// 新增部分
conn.setHostnameVerifier(new SslHandshakeExc_NsanMatchingIp().new TrustAnyHostnameVerifier());
conn.connect();
InputStream is = conn.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
is.close();
}
// 定制Verifier
public class TrustAnyHostnameVerifier implements HostnameVerifier {
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
}
- JSP中实现,不进行主机名验证
//1.创建URL对象
URL url = new URL(validateurl);
//2.通过URL对象获取能够访问Http协议的类HttpURLConnection
HttpsURLConnection HttpsURLConnection = (HttpsURLConnection) url.openConnection();
//3.封装请求相关参数
HttpsURLConnection.setRequestMethod("GET"); //请求方式
HttpsURLConnection.setRequestProperty("version", "1.0");//设置请求头
HttpsURLConnection.setConnectTimeout(6*1000); //设置超时时间
HttpsURLConnection.setReadTimeout(6*1000);
// 自定义 HostnameVerifier
HostnameVerifier allHostsValid = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true; // 不进行主机名验证
}
};
HttpsURLConnection.setHostnameVerifier(allHostsValid);
//4.建立连接
HttpsURLConnection.connect();
......
8.2 请求响应XML格式数据解析方式
当项目中做SSO集成时,在对方接口中响应得到数据如下所示 XML格式数据。
可以通过 java.util.regex.Pattern、java.util.regex.Matcher
两个类来实现获取 XML响应数据中的参数。
Pattern pt = Pattern.compile("<cas:user>(.*)</cas:user>");
// 将远程调用接口响应数据 byteStream 传入
Matcher match = pt.matcher(byteStream);
username = match.group(0);
这段代码的主要功能是使用正则表达式从字符串中提取 cas:user 标签内的内容。具体步骤如下:
- 创建一个正则表达式模式,用于匹配 cas:user 标签及其内容。
- 创建一个匹配器对象,用于在字符串中查找与模式匹配的子序列。
- 通过匹配器对象获取捕获组的内容,即 cas:user 标签内的内容。
8.3 获取当前访问路径信息
通过request,来获取当前的访问路径的协议、地址 以及端口信息!
//当前的访问路径的协议及地址和端口号
String service = request.getScheme() + "://" + request.getServerName();
int port = request.getServerPort();
if (port!=80 && port!=443) {
//默认端口,不加在跳转路径下
service += ":" + request.getServerPort();
}
service += request.getRequestURI();
PS:HTTPS默认访问IP地址时,对应映射端口为 443 !