如何經過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咱們只須要定義服務綁定接口,以申明式的方法,優雅而簡單的實現了服務調用。