如何经过Java发送HTTP请求,通俗点讲,如何经过Java(模拟浏览器)发送HTTP请求。java
Java有原生的API可用于发送HTTP请求,即java.net.URL、java.net.URLConnection,这些API很好用、很经常使用,但不够简便;web
因此,也流行有许多Java HTTP请求的framework,如,Apache的HttpClient。spring
httpclient以外RPC 以及队列的使用看能够说也是愈来愈普遍了。json
在netty等NIO框架由于须要高效的传输因此每每选择RPC,队列则用于回调以及设备消息之间的传递。浏览器
Http这个经久不衰的大佬天然不用多说,简单,支持普遍,高度兼容性。
服务器
HttpClientapp
HttpClient相比传统JDK自带的URLConnection,增长了易用性和灵活性,使得客户端发送Http请求变得容易。负载均衡
HttpClient使用:框架
使用HttpClient发送请求、接收响应很简单,通常须要以下几步便可。socket
1. 建立HttpClient对象。
2. 建立请求方法的实例,并指定请求URL。若是须要发送GET请求,建立HttpGet对象;若是须要发送POST请求,建立HttpPost对象。
3. 若是须要发送请求参数,可调用HttpGet、HttpPost共同的setParams(HetpParams params)方法来添加请求参数;对于HttpPost对象而言,也可调用setEntity(HttpEntity entity)方法来设置请求参数。
4. 调用HttpClient对象的execute(HttpUriRequest request)发送请求,该方法返回一个HttpResponse。
5. 调用HttpResponse的getAllHeaders()、getHeaders(String name)等方法可获取服务器的响应头;调用HttpResponse的getEntity()方法可获取HttpEntity对象,该对象包装了服务器的响应内容。程序可经过该对象获取服务器的响应内容。
6. 释放链接。不管执行方法是否成功,都必须释放链接
RestTemplate本是spring-web项目中的一个REST客户端,它遵循REST的设计原则,提供简单的API让咱们能够调用HTTP服务。底层是对httpclient进行了封装。
ps:建议使用exchange ,其余方法都是对execute进行了封装,都拥有各自的局限性。
RestTemplate能够用来作负载均衡,RestTemplate自己不具备负载均衡的功能,该类也与Spring Cloud没有关系,但为什么加入@LoadBalanced注解后,一个RestTemplate实例就具备负载均衡的功能呢?
实际上这要得益于RestTemplate的拦截器功能。
下面举例:基于本身配置的HttpClient,建立一个可负载均衡的RestTemplate
HttpClientProperties
HttpClient的配置信息
@ConfigurationProperties(prefix="spring.httpclient") public class HttpClientProperties { private Integer connectTimeOut = 1000; private Integer socketTimeOut = 1000000; private String agent = "agent"; private Integer maxConnPerRoute = 10; private Integer maxConnTotaol = 50; public Integer getConnectTimeOut() { return connectTimeOut; } public void setConnectTimeOut(Integer connectTimeOut) { this.connectTimeOut = connectTimeOut; } public Integer getSocketTimeOut() { return socketTimeOut; } public void setSocketTimeOut(Integer socketTimeOut) { this.socketTimeOut = socketTimeOut; } public String getAgent() { return agent; } public void setAgent(String agent) { this.agent = agent; } public Integer getMaxConnPerRoute() { return maxConnPerRoute; } public void setMaxConnPerRoute(Integer maxConnPerRoute) { this.maxConnPerRoute = maxConnPerRoute; } public Integer getMaxConnTotaol() { return maxConnTotaol; } public void setMaxConnTotaol(Integer maxConnTotaol) { this.maxConnTotaol = maxConnTotaol; } }
HttpClientAutoConfiguration
根据HttpClient.class是否存在,bean是否存在,自动进行配置
@Configuration @ConditionalOnClass({HttpClient.class}) @EnableConfigurationProperties(HttpClientProperties.class) public class HttpClientAutoConfiguration { private final HttpClientProperties properties; public HttpClientAutoConfiguration(HttpClientProperties properties){ this.properties = properties; } /** * httpclient bean 的定义 * @return */ @Bean @ConditionalOnMissingBean(HttpClient.class) public HttpClient httpClient() { RequestConfig requestConfig = RequestConfig.custom() .setConnectTimeout(properties.getConnectTimeOut()) .setSocketTimeout(properties.getSocketTimeOut()).build();// 构建requestConfig HttpClient client = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig) .setUserAgent(properties.getAgent()) .setMaxConnPerRoute(properties.getMaxConnPerRoute()) .setMaxConnTotal(properties.getMaxConnTotaol()) .build(); return client; } }
@ConditionalOnClass:该注解的参数对应的类必须存在,不然不解析该注解修饰的配置类;
@ConditionalOnMissingBean:该注解表示,若是存在它修饰的类的bean,则不须要再建立这个bean;能够给该注解传入参数例如@ConditionOnMissingBean(name = "example"),这个表示若是name为“example”的bean存在,这该注解修饰的代码块不执行。
RestAutoConfig
建立负载均衡和直连的RestTemplate
@Configuration public class RestAutoConfig { public static class RestTemplateConfig { @Bean//负载均衡的restTemplate @LoadBalanced //spring 对restTemplate bean进行定制,加入loadbalance拦截器进行ip:port的替换 //"http://user/getusername,就能解析成http://127.0.0.1:8083//getusername RestTemplate lbRestTemplate(HttpClient httpclient) { RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory(httpclient)); template.getMessageConverters().add(0,new StringHttpMessageConverter(Charset.forName("utf-8"))); template.getMessageConverters().add(1,new FastJsonHttpMessageConvert5()); return template; } @Bean //直连的restTemplat,这时只能使用http://127.0.0.1:8083//getusername地址,不能解析http://user/getusername RestTemplate directRestTemplate(HttpClient httpclient) { RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory(httpclient)); template.getMessageConverters().add(0,new StringHttpMessageConverter(Charset.forName("utf-8"))); template.getMessageConverters().add(1,new FastJsonHttpMessageConvert5()); return template; } // FastJsonHttpMessageConvert4有一个bug,它是默认支持MediaType.ALL,spring在处理MediaType.ALL的时候会识别成字节流,而不是json,这里就对他进行改造和处理 public static class FastJsonHttpMessageConvert5 extends FastJsonHttpMessageConverter4{ static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); public FastJsonHttpMessageConvert5(){ setDefaultCharset(DEFAULT_CHARSET); setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON,new MediaType("application","*+json"))); } } } }
调用RestTemplate的默认构造函数,RestTemplate对象在底层经过使用java.net包下的实现建立HTTP 请求,能够经过使用ClientHttpRequestFactory指定不一样的HTTP请求方式。
ClientHttpRequestFactory接口主要提供了两种实现方式
GenericRest
既支持直连又支持服务发现的调用
/** * 既支持直连又支持服务发现的调用 * */ @Service public class GenericRest { @Autowired @Qualifier("lbRestTemplate") private RestTemplate lbRestTemplate; @Autowired @Qualifier("directRestTemplate") private RestTemplate directRestTemplate; private static final String directFlag = "direct://"; //返回的泛型用ParameterizedTypeReference<T>来指定 public <T> ResponseEntity<T> post(String url,Object reqBody,ParameterizedTypeReference<T> responseType){ RestTemplate template = getRestTemplate(url); url = url.replace(directFlag, ""); return template.exchange(url, HttpMethod.POST,new HttpEntity(reqBody),responseType); } public <T> ResponseEntity<T> get(String url,ParameterizedTypeReference<T> responseType){ RestTemplate template = getRestTemplate(url); url = url.replace(directFlag, ""); return template.exchange(url, HttpMethod.GET,HttpEntity.EMPTY,responseType); } private RestTemplate getRestTemplate(String url) { if (url.contains(directFlag)) { return directRestTemplate; }else { return lbRestTemplate; } } }
exchange支持‘含参数的类型’(即泛型类)做为返回类型,该特性经过‘ParameterizedTypeReference<T>responseType’描述。
进行服务调用:
public List<User> getUserList(User query) { ResponseEntity<RestResponse<List<User>>> resultEntity = rest.post("http://"+ userServiceName + "/user/getList",query, new ParameterizedTypeReference<RestResponse<List<User>>>() {}); RestResponse<List<User>> restResponse = resultEntity.getBody(); if (restResponse.getCode() == 0) { return restResponse.getResult(); }else { return null; } }
SpringCloud Feign—申明式服务调用
虽然RestTemplate已经能够将请求拦截来实现对依赖服务的接口调用,并对Http请求进行封装处理,造成一套模板化的调用方法,可是对服务依赖的调用可能不仅一处,一个接口都会被屡次调用,因此咱们会像前面那样针对各个微服务字形封装一些客户端接口调用类来包装这些依赖服务的调用。
因为RestTemplate的封装,几乎每个调用都是简单的模板化内容,Feign在此基础上作了进一步的封装,由它来帮助咱们定义和实现依赖服务接口的定义。
在服务消费者建立服务调用接口,经过@FeignClient注解指定服务名来绑定服务,而后再使用SpringMVC的注解来绑定具体该服务提供的REST接口。
@FeignClient("biz-service-0") public interface UserClient { @RequestMapping(method = RequestMethod.GET, value = "/getuser") public User getuserinfo(); @RequestMapping(method = RequestMethod.GET, value = "/getuser") public String getuserinfostr(); }
在服务消费者的web层进行调用:
@RestController public class UserController { @Autowired UserClient userClient; @RequestMapping(value = "/getuserinfo", method = RequestMethod.GET) public User getuserinfo() { return userClient.getuserinfo(); } @RequestMapping(value = "/getuserinfostr", method = RequestMethod.GET) public String getuserinfostr() { return userClient.getuserinfostr(); }
经过Feign咱们只须要定义服务绑定接口,以申明式的方法,优雅而简单的实现了服务调用。