HTTP 协议可能是现在 Internet 上使用得最多、最重要的协议了,越来越多的 Java 应用程序需要直接通过 HTTP 协议来访问网络资源。虽然在 JDK 的 java net包中已经提供了访问 HTTP 协议的基本功能,但是对于大部分应用程序来说,JDK 库本身提供的功能还不够丰富和灵活。HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。HttpClient 已经应用在很多的项目中,比如 Apache Jakarta 上很著名的另外两个开源项目 Cactus 和 HTMLUnit 都使用了 HttpClient。
org.apache.httpcomponents
httpclient
/**
* 1.测试HttpClient中的get请求
* 步骤:
* 1.实例化httpClient对象
* 2.确定访问url地址
* 3.封装请求的类型 get/post/put/delete
* 4.发起http请求,获取响应对象 response.
* 5.校验返回对象的状态码信息.
* 200表示成功
* 400请求的参数异常
* 404请求路径不匹配
* 406请求的返回值类型异常
* 500后台服务器运行异常
* 504请求超时. 后台服务器宕机/遇忙
* 6.如果状态码信息为200,则动态的获取响应数据.
* 7.获取返回值数据,之后进行业务调用.
* @throws IOException
* @throws ClientProtocolException
*
*/
@Test
public void testGet() throws ClientProtocolException, IOException {
HttpClient httpClient = HttpClients.createDefault();
String url = "https://www.baidu.com";
HttpGet httpGet = new HttpGet(url);
HttpPost httpPost = new HttpPost(url);
HttpResponse httpResponse = httpClient.execute(httpPost);
if(httpResponse.getStatusLine().getStatusCode()==200) {
//表示用户请求成功!!
HttpEntity entity = httpResponse.getEntity(); //返回值数据的实体对象
String json = EntityUtils.toString(entity,"utf-8");
System.out.println(json);
}else {
//一定请求有误.
System.out.println("服务器正忙,请稍后!!!");
}
}
#最大连接数
http.maxTotal=1000
#并发数
http.defaultMaxPerRoute=20
#创建连接的最长时间
http.connectTimeout=5000
#从连接池中获取到连接的最长时间
http.connectionRequestTimeout=500
#数据传输的最长时间
http.socketTimeout=5000
#提交请求前测试连接是否可用
http.staleConnectionCheckEnabled=true
@Configuration
@PropertySource(value="classpath:/properties/httpClient.properties")
public class HttpClientConfig {
@Value("${http.maxTotal}")
private Integer maxTotal; //最大连接数
@Value("${http.defaultMaxPerRoute}")
private Integer defaultMaxPerRoute; //最大并发链接数
@Value("${http.connectTimeout}")
private Integer connectTimeout; //创建链接的最大时间
@Value("${http.connectionRequestTimeout}")
private Integer connectionRequestTimeout; //链接获取超时时间
@Value("${http.socketTimeout}")
private Integer socketTimeout; //数据传输最长时间
@Value("${http.staleConnectionCheckEnabled}")
private boolean staleConnectionCheckEnabled; //提交时检查链接是否可用
//定义httpClient链接池
@Bean(name="httpClientConnectionManager")
public PoolingHttpClientConnectionManager getPoolingHttpClientConnectionManager() {
PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager();
manager.setMaxTotal(maxTotal); //设定最大链接数
manager.setDefaultMaxPerRoute(defaultMaxPerRoute); //设定并发链接数
return manager;
}
//定义HttpClient
/**
* 实例化连接池,设置连接池管理器。
* 这里需要以参数形式注入上面实例化的连接池管理器
@Qualifier 指定bean标签进行注入
*/
@Bean(name = "httpClientBuilder")
public HttpClientBuilder getHttpClientBuilder(@Qualifier("httpClientConnectionManager")PoolingHttpClientConnectionManager httpClientConnectionManager){
//HttpClientBuilder中的构造方法被protected修饰,所以这里不能直接使用new来实例化一个HttpClientBuilder,可以使用HttpClientBuilder提供的静态方法create()来获取HttpClientBuilder对象
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
httpClientBuilder.setConnectionManager(httpClientConnectionManager);
return httpClientBuilder;
}
/**
* 注入连接池,用于获取httpClient
* @param httpClientBuilder
* @return
*/
@Bean
public CloseableHttpClient getCloseableHttpClient(@Qualifier("httpClientBuilder") HttpClientBuilder httpClientBuilder){
return httpClientBuilder.build();
}
/**
* Builder是RequestConfig的一个内部类
* 通过RequestConfig的custom方法来获取到一个Builder对象
* 设置builder的连接信息
* @return
*/
@Bean(name = "builder")
public RequestConfig.Builder getBuilder(){
RequestConfig.Builder builder = RequestConfig.custom();
return builder.setConnectTimeout(connectTimeout)
.setConnectionRequestTimeout(connectionRequestTimeout)
.setSocketTimeout(socketTimeout)
.setStaleConnectionCheckEnabled(staleConnectionCheckEnabled);
}
/**
* 使用builder构建一个RequestConfig对象
* @param builder
* @return
*/
@Bean
public RequestConfig getRequestConfig(@Qualifier("builder") RequestConfig.Builder builder){
return builder.build();
}
}
@Component //交给spring容器管理,定时将超时的链接关闭
public class HttpClientClose extends Thread{
@Autowired
private PoolingHttpClientConnectionManager manage;
private volatile boolean shutdown; //开关 volatitle表示多线程可变数据,一个线程修改,其他线程立即修改
public HttpClientClose() {
///System.out.println("执行构造方法,实例化对象");
//线程开启启动
this.start();
}
@Override
public void run() {
try {
//如果服务没有关闭,执行线程
while(!shutdown) {
synchronized (this) {
wait(5000); //等待5秒
//System.out.println("线程开始执行,关闭超时链接");
//关闭超时的链接
PoolStats stats = manage.getTotalStats();
int av = stats.getAvailable(); //获取可用的线程数量
int pend = stats.getPending(); //获取阻塞的线程数量
int lea = stats.getLeased(); //获取当前正在使用的链接数量
int max = stats.getMax();
//System.out.println("max/"+max+": av/"+av+": pend/"+pend+": lea/"+lea);
manage.closeExpiredConnections();
}
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}
super.run();
}
//关闭清理无效连接的线程
@PreDestroy //容器关闭时执行该方法.
public void shutdown() {
shutdown = true;
synchronized (this) {
//System.out.println("关闭全部链接!!");
notifyAll(); //全部从等待中唤醒.执行关闭操作;
}
}
}
@Service
public class HttpClientService {
@Autowired
private CloseableHttpClient httpClient;
@Autowired
private RequestConfig requestConfig;
/**
* 编辑一个HttpClientAPI,简化程序远程调用的过程
* @param url
* @param params
* @param charset
* @return
*/
public String doGet(String url,Map params,String charset) {
//1.判断字符集编码是否为空
if(StringUtils.isEmpty(charset)) {
charset="UTF-8";
}
//2.校验Map中是否有参数 Map>
// 有参数: http://www.jt.com/findItem?id=1&name=brrby
if(params!=null) {
//需要进行参数的拼接 将Map集合遍历,动态获取key=value
url+="?";
for (Map.Entry i : params.entrySet()) {
String key=i.getKey();
String value=i.getValue();
//拼接参数
url+=key+"="+value+"&";
}
//http://www.jt.com/findItem?id=1&name=brrby&
url=url.substring(0,url.length()-1);
}
//3.定义用户的请求类型
HttpGet httpGet=new HttpGet(url);
//为get请求添加超时时间
httpGet.setConfig(requestConfig);
//4.发起http请求
String result=null;
try {
HttpResponse httpResponse = httpClient.execute(httpGet);
//判断状态码是否正确
if(httpResponse.getStatusLine().getStatusCode()==200) {
HttpEntity entity=httpResponse.getEntity();
result=EntityUtils.toString(entity,charset);
}
}catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
throw new RuntimeException();
}
return result;
}
//为不同的用户提供不同的方法支持
public String doGet(String url) {
return doGet(url,null,null);
}
public String doGet(String url,Map params) {
return doGet(url,params,null);
}
//实现httpClient POST提交
public String doPost(String url,Map params,String charset){
String result = null;
//1.定义请求类型
HttpPost post = new HttpPost(url);
post.setConfig(requestConfig); //定义超时时间
//2.判断字符集是否为null
if(StringUtils.isEmpty(charset)){
charset = "UTF-8";
}
//3.判断用户是否传递参数
if(params !=null){
//3.2准备List集合信息
List parameters =
new ArrayList<>();
//3.3将数据封装到List集合中
for (Map.Entry entry : params.entrySet()) {
parameters.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
//3.1模拟表单提交
try {
UrlEncodedFormEntity formEntity =
new UrlEncodedFormEntity(parameters,charset); //采用u8编码
//3.4将实体对象封装到请求对象中
post.setEntity(formEntity);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
//4.发送请求
try {
CloseableHttpResponse response =
httpClient.execute(post);
//4.1判断返回值状态
if(response.getStatusLine().getStatusCode() == 200) {
//4.2表示请求成功
result = EntityUtils.toString(response.getEntity(),charset);
}else{
System.out.println("获取状态码信息:"+response.getStatusLine().getStatusCode());
throw new RuntimeException();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return result;
}
public String doPost(String url){
return doPost(url, null, null);
}
public String doPost(String url,Map params){
return doPost(url, params, null);
}
public String doPost(String url,String charset){
return doPost(url, null, charset);
}
}
@RestController
@RequestMapping("/web/item")
public class WebItemController {
@Autowired
private ItemService itemService;
/**
* 根据前台url动态获取商品数据
* http://manage.jt.com/web/item/findItemById?itmeId=562379
*/
@RequestMapping("/findItemById")
public Item findItemById(Long itemId) {
return itemService.findItemById(itemId);
}
}
/**
* 测试后台动态获取数据,是否成功!!!
* http://manage.jt.com/web/item/findItemById?itemId=562379
*/
//远程获取服务器数据,并且转化为自己的对象.
@Test
public void testFindItem() {
String url = "http://manage.jt.com/web/item/findItemById";
Map params = new HashMap<>();
params.put("itemId", "562379");
String result = httpClientService.doGet(url, params);
//将JSON转化为对象
Item item = ObjectMapperUtil.toObj(result, Item.class);
System.out.println(item);
}
JSONP(JSON with Padding)是JSON的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题。由于同源策略,一般来说位于 server1.example.com 的网页无法与不是 server1.example.com的服务器沟通,而 HTML 的< script > 元素是一个例外。利用 < script > 元素的这个开放策略,网页可以得到从其他来源动态产生的 JSON 资料,而这种使用模式就是所谓的 JSONP。用 JSONP 抓到的资料并不是 JSON,而是任意的JavaScript,用 JavaScript 直译器执行而不是用 JSON 解析器解析。
编辑a.html在a项目
JSONP测试
JSON跨域请求测试
在后台manage项目里编辑一个专门接收a项目发来的请求的RestController
@RestController
public class JSONPController {
//http://manage.jt.com/web/testJSONP
/**
* JSONPObject是跨域访问的API
* function:回调函数的名称
* value: 需要返回的对象
*/
@RequestMapping("/web/testJSONP")
public JSONPObject jsonp(String callback) {
ItemDesc itemDesc=new ItemDesc();
itemDesc.setItemId(5050L).setItemDesc("我是!");
return new JSONPObject(callback, itemDesc);
}
}
面向服务的架构(SOA)是一个组件模型,它将应用程序的不同功能单元(称为服务)进行拆分,并通过这些服务之间定义良好的接口和协议联系起来。接口是采用中立的方式进行定义的,它应该独立于实现服务的硬件平台、操作系统和编程语言。这使得构件在各种各样的系统中的服务可以以一种统一和通用的方式进行交互。
RPC是远程过程调用(Remote Procedure Call)的缩写形式。也就是说两台服务器A、B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数或方法。由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。SAP系统RPC调用的原理其实很简单,有一些类似于三层构架的C/S系统,第三方的客户程序通过接口调用SAP内部的标准或自定义函数,获得函数返回的数据进行处理后显示或打印。
RPC 和 SOA 一样也不是规范,不是协议,是一种技术思想,一种理念。 比如耳熟能祥的 HTTP 和 WebService 就是 RPC 思想的一种很好的体现方式。