欧美一级特黄大片做受成人-亚洲成人一区二区电影-激情熟女一区二区三区-日韩专区欧美专区国产专区

利用springboot怎么實(shí)現(xiàn)一個(gè)代理分發(fā)服務(wù)功能-創(chuàng)新互聯(lián)

這篇文章給大家介紹利用springboot怎么實(shí)現(xiàn)一個(gè)代理分發(fā)服務(wù)功能,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助。

成都創(chuàng)新互聯(lián)公司提供成都做網(wǎng)站、網(wǎng)站制作、網(wǎng)頁(yè)設(shè)計(jì),品牌網(wǎng)站制作,一元廣告等致力于企業(yè)網(wǎng)站建設(shè)與公司網(wǎng)站制作,10多年的網(wǎng)站開(kāi)發(fā)和建站經(jīng)驗(yàn),助力企業(yè)信息化建設(shè),成功案例突破近千家,是您實(shí)現(xiàn)網(wǎng)站建設(shè)的好選擇.

思路:客戶(hù)端發(fā)送請(qǐng)求,由代理服務(wù)端通過(guò)匹配請(qǐng)求內(nèi)容,然后在作為代理去訪(fǎng)問(wèn)真實(shí)的服務(wù)器,最后由真實(shí)的服務(wù)器將響應(yīng)返回給代理,代理再返回給瀏覽器。

技術(shù):說(shuō)道反向代理,可能首先想到的就是nginx。不過(guò)在我們的需求中,對(duì)于轉(zhuǎn)發(fā)過(guò)程有更多需求:

  • 需要操作session,根據(jù)session的取值決定轉(zhuǎn)發(fā)行為

  • 需要修改Http報(bào)文,增加Header或是QueryString

第一點(diǎn)決定了我們的實(shí)現(xiàn)必定是基于Servlet的。springboot提供的ProxyServlet就可以滿(mǎn)足我們的要求,ProxyServlet直接繼承自HttpServlet,采用異步的方式調(diào)用內(nèi)部服務(wù)器,因此效率上不會(huì)有什么問(wèn)題,并且各種可重載的函數(shù)也提供了比較強(qiáng)大的定制機(jī)制。

實(shí)現(xiàn)過(guò)程

引入依賴(lài)

<dependency>
  <groupId>org.mitre.dsmiley.httpproxy</groupId>
  <artifactId>smiley-http-proxy-servlet</artifactId>
  <version>1.11</version>
</dependency>

構(gòu)建一個(gè)配置類(lèi)

@Configuration
public class ProxyServletConfiguration {
 
 private final static String REPORT_URL = "/newReport_proxy/*";
 
 @Bean
 public ServletRegistrationBean proxyServletRegistration() {
  List<String> list = new ArrayList<>();
  list.add(REPORT_URL); //如果需要匹配多個(gè)url則定義好放到list中即可
  ServletRegistrationBean registrationBean = new ServletRegistrationBean();
  registrationBean.setServlet(new ThreeProxyServlet());
  registrationBean.setUrlMappings(list);
  //設(shè)置默認(rèn)網(wǎng)址以及參數(shù)
  Map<String, String> params = ImmutableMap.of("targetUri", "null", "log", "true");
  registrationBean.setInitParameters(params);
  return registrationBean;
 }
}

編寫(xiě)代理邏輯

public class ThreeProxyServlet extends ProxyServlet {
 
 private static final long serialVersionUID = -9125871545605920837L;
 
 private final Logger logger = LoggerFactory.getLogger(ThreeProxyServlet.class);
 public String proxyHttpAddr;
 public String proxyName;
 
 private ResourceBundle bundle =null;
 @Override
 public void init() throws ServletException {
  bundle = ResourceBundle.getBundle("prop");
  super.init();
 }
 
 @Override
 protected void service(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws ServletException, IOException {
 
  // 初始切換路徑
  String requestURI = servletRequest.getRequestURI();
  proxyName = requestURI.split("/")[2];
  //根據(jù)name匹配域名到properties文件中獲取
  proxyHttpAddr = bundle.getString(proxyName);
 
  String url = proxyHttpAddr;
  if (servletRequest.getAttribute(ATTR_TARGET_URI) == null) {
   servletRequest.setAttribute(ATTR_TARGET_URI, url);
  }
 
  if (servletRequest.getAttribute(ATTR_TARGET_HOST) == null) {
   URL trueUrl = new URL(url);
   servletRequest.setAttribute(ATTR_TARGET_HOST, new HttpHost(trueUrl.getHost(), trueUrl.getPort(), trueUrl.getProtocol()));
  }
 
  String method = servletRequest.getMethod();
  // 替換多余路徑
  String proxyRequestUri = this.rewriteUrlFromRequest(servletRequest);
 
  Object proxyRequest;
  if (servletRequest.getHeader("Content-Length") == null && servletRequest.getHeader("Transfer-Encoding") == null) {
   proxyRequest = new BasicHttpRequest(method, proxyRequestUri);
  } else {
   proxyRequest = this.newProxyRequestWithEntity(method, proxyRequestUri, servletRequest);
  }
 
  this.copyRequestHeaders(servletRequest, (HttpRequest)proxyRequest);
  setXForwardedForHeader(servletRequest, (HttpRequest)proxyRequest);
  HttpResponse proxyResponse = null;
  try {
   proxyResponse = this.doExecute(servletRequest, servletResponse, (HttpRequest)proxyRequest);
   int statusCode = proxyResponse.getStatusLine().getStatusCode();
   servletResponse.setStatus(statusCode, proxyResponse.getStatusLine().getReasonPhrase());
   this.copyResponseHeaders(proxyResponse, servletRequest, servletResponse);
   if (statusCode == 304) {
    servletResponse.setIntHeader("Content-Length", 0);
   } else {
    this.copyResponseEntity(proxyResponse, servletResponse, (HttpRequest)proxyRequest, servletRequest);
   }
  } catch (Exception var11) {
   this.handleRequestException((HttpRequest)proxyRequest, var11);
  } finally {
   if (proxyResponse != null) {
    EntityUtils.consumeQuietly(proxyResponse.getEntity());
   }
 
  }
 }
 
 @Override
 protected HttpResponse doExecute(HttpServletRequest servletRequest, HttpServletResponse servletResponse, HttpRequest proxyRequest) throws IOException {
  HttpResponse response = null;
  // 攔截校驗(yàn) 可自定義token過(guò)濾
  //String token = servletRequest.getHeader("ex_proxy_token");
  // 代理服務(wù)鑒權(quán)邏輯
  this.getAuthString(proxyName,servletRequest,proxyRequest);
  //執(zhí)行代理轉(zhuǎn)發(fā)
  try {
   response = super.doExecute(servletRequest, servletResponse, proxyRequest);
  } catch (IOException e) {
   e.printStackTrace();
  }
  return response;
 }
}

增加一個(gè)properties配置文件

上邊的配置簡(jiǎn)單介紹一下,對(duì)于/newReport_proxy/* 這樣的寫(xiě)法,意思就是當(dāng)你的請(qǐng)求路徑以newReport_proxy 開(kāi)頭,比如http://localhost:8080/newReport_proxy/test/get1 這樣的路徑,它請(qǐng)求的真實(shí)路徑是https://www.baidu.com/test/get1 。主要就是將newReport_proxy 替換成對(duì)應(yīng)的被代理路徑而已,* 的意思就是實(shí)際請(qǐng)求代理項(xiàng)目中接口的路徑,這種配置對(duì)get 、post 請(qǐng)求都有效。

遇到問(wèn)題

按如上配置,在執(zhí)行代理轉(zhuǎn)發(fā)的時(shí)候需要對(duì)轉(zhuǎn)發(fā)的代理服務(wù)器的接口進(jìn)行鑒權(quán),具體鑒權(quán)方案調(diào)用就是 "this.getAuthString(proxyName,servletRequest,proxyRequest);”這段代碼。代理服務(wù)的鑒權(quán)邏輯根據(jù)入?yún)?token值之后按算法計(jì)算一個(gè)值,之后進(jìn)行放到header中傳遞。那么這就遇到了一個(gè)問(wèn)題,就是當(dāng)前端采用requestBody的方式進(jìn)行調(diào)用請(qǐng)求時(shí)服務(wù)1進(jìn)行代理轉(zhuǎn)發(fā)的時(shí)候會(huì)出現(xiàn)錯(cuò)誤:

利用springboot怎么實(shí)現(xiàn)一個(gè)代理分發(fā)服務(wù)功能

一直卡在執(zhí)行 doExecute()方法。一頓操作debug后定位到一個(gè)點(diǎn),也就是最后進(jìn)行觸發(fā)進(jìn)行執(zhí)行代理服務(wù)調(diào)用的點(diǎn):

利用springboot怎么實(shí)現(xiàn)一個(gè)代理分發(fā)服務(wù)功能

在上圖位置拋了異常,上圖中i的值為-1,說(shuō)明這個(gè)sessionBuffer中沒(méi)有數(shù)據(jù)了,讀取不到了所以返回了-1。那么這個(gè)sessionBuffer是個(gè)什么東西呢?這個(gè)東西翻譯過(guò)來(lái)指的是會(huì)話(huà)輸入緩沖區(qū),會(huì)阻塞連接。 與InputStream類(lèi)相似,也提供讀取文本行的方法。也就是通過(guò)這個(gè)類(lèi)將對(duì)應(yīng)請(qǐng)求的數(shù)據(jù)流發(fā)送給目標(biāo)服務(wù)。這個(gè)位置出錯(cuò)說(shuō)明這個(gè)要發(fā)送的數(shù)據(jù)流沒(méi)有了,那么在什么時(shí)候?qū)⒄?qǐng)求的數(shù)據(jù)流信息給弄沒(méi)了呢?那就是我們加點(diǎn)鑒權(quán)邏輯,鑒權(quán)邏輯需要獲取requestBody中的參數(shù),去該參數(shù)是從request對(duì)象中通過(guò)流讀取的。這個(gè)問(wèn)題我們也見(jiàn)過(guò)通常情況下,HttpServletRequst 中的 body 內(nèi)容只會(huì)讀取一次,但是可能某些情境下可能會(huì)讀取多次,由于 body 內(nèi)容是以流的形式存在,所以第一次讀取完成后,第二次就無(wú)法讀取了,一個(gè)典型的場(chǎng)景就是 Filter 在校驗(yàn)完成 body 的內(nèi)容后,業(yè)務(wù)方法就無(wú)法繼續(xù)讀取流了,導(dǎo)致解析報(bào)錯(cuò)。

最終實(shí)現(xiàn)

思路:用裝飾器來(lái)修飾一下 request,使其可以包裝讀取的內(nèi)容,供多次讀取。其實(shí)spring boot提供了一個(gè)簡(jiǎn)單的封裝器ContentCachingRequestWrapper,從源碼上看這個(gè)封裝器并不實(shí)用,沒(méi)有封裝http的底層流ServletInputStream信息,所以在這個(gè)場(chǎng)景下還是不能重復(fù)獲取對(duì)應(yīng)的流信息。

參照ContentCachingRequestWrapper類(lèi)實(shí)現(xiàn)一個(gè)stream緩存

public class CacheStreamHttpRequest extends HttpServletRequestWrapper {
 private static final Logger LOGGER = LoggerFactory.getLogger(CacheStreamHttpRequest.class);
 private final ByteArrayOutputStream cachedContent;
 private Map<String, String[]> cachedForm;
 
 @Nullable
 private ServletInputStream inputStream;
 
 public CacheStreamHttpRequest(HttpServletRequest request) {
  super(request);
  this.cachedContent = new ByteArrayOutputStream();
  this.cachedForm = new HashMap<>();
  cacheData();
 }
 
 @Override
 public ServletInputStream getInputStream() throws IOException {
  this.inputStream = new RepeatReadInputStream(cachedContent.toByteArray());
  return this.inputStream;
 }
 
 @Override
 public String getCharacterEncoding() {
  String enc = super.getCharacterEncoding();
  return (enc != null ? enc : WebUtils.DEFAULT_CHARACTER_ENCODING);
 }
 
 @Override
 public BufferedReader getReader() throws IOException {
   return new BufferedReader(new InputStreamReader(getInputStream(), getCharacterEncoding()));
 }
 
 @Override
 public String getParameter(String name) {
  String value = null;
  if (isFormPost()) {
   String[] values = cachedForm.get(name);
   if (null != values && values.length > 0) {
    value = values[0];
   }
  }
 
  if (StringUtils.isEmpty(value)) {
   value = super.getParameter(name);
  }
 
  return value;
 }
 
 @Override
 public Map<String, String[]> getParameterMap() {
  if (isFormPost() && !CollectionUtils.sizeIsEmpty(cachedForm)) {
   return cachedForm;
  }
 
  return super.getParameterMap();
 }
 
 @Override
 public Enumeration<String> getParameterNames() {
  if (isFormPost() && !CollectionUtils.sizeIsEmpty(cachedForm)) {
   return Collections.enumeration(cachedForm.keySet());
  }
 
  return super.getParameterNames();
 }
 
 @Override
 public String[] getParameterValues(String name) {
  if (isFormPost() && !CollectionUtils.sizeIsEmpty(cachedForm)) {
   return cachedForm.get(name);
  }
 
  return super.getParameterValues(name);
 }
 
 private void cacheData() {
  try {
   if (isFormPost()) {
    this.cachedForm = super.getParameterMap();
   } else {
    ServletInputStream inputStream = super.getInputStream();
    IOUtils.copy(inputStream, this.cachedContent);
   }
  } catch (IOException e) {
   LOGGER.warn("[RepeatReadHttpRequest:cacheData], error: {}", e.getMessage());
  }
 
 }
 
 private boolean isFormPost() {
  String contentType = getContentType();
  return (contentType != null &&
    (contentType.contains(MediaType.APPLICATION_FORM_URLENCODED_VALUE) ||
      contentType.contains(MediaType.MULTIPART_FORM_DATA_VALUE)) &&
    HttpMethod.POST.matches(getMethod()));
 }
 
 private static class RepeatReadInputStream extends ServletInputStream {
  private final ByteArrayInputStream inputStream;
 
  public RepeatReadInputStream(byte[] bytes) {
   this.inputStream = new ByteArrayInputStream(bytes);
  }
 
  @Override
  public int read() throws IOException {
   return this.inputStream.read();
  }
 
  @Override
  public int readLine(byte[] b, int off, int len) throws IOException {
   return this.inputStream.read(b, off, len);
  }
 
  @Override
  public boolean isFinished() {
   return this.inputStream.available() == 0;
  }
 
  @Override
  public boolean isReady() {
   return true;
  }
 
  @Override
  public void setReadListener(ReadListener listener) {
 
  }
 }
}

如上類(lèi)核心邏輯是通過(guò)cacheData() 方法進(jìn)行將 request對(duì)象緩存,存儲(chǔ)到ByteArrayOutputStream類(lèi)中,當(dāng)在調(diào)用request對(duì)象獲取getInputStream()方法時(shí)從ByteArrayOutputStream類(lèi)中寫(xiě)回InputStream核心代碼:

 @Override
 public ServletInputStream getInputStream() throws IOException {
  this.inputStream = new RepeatReadInputStream(cachedContent.toByteArray());
  return this.inputStream;
 }

使用這個(gè)封裝后的request時(shí)需要配合Filter對(duì)原有的request進(jìn)行替換,注冊(cè)Filter并在調(diào)用鏈中將原有的request換成該封裝類(lèi)。代碼:

//chain.doFilter(request, response); 
//換掉原來(lái)的request對(duì)象 用new RepeatReadHttpRequest((HttpServletRequest) request) 因?yàn)楹笳吡髦杏删彺鏀r截器httprequest替換 可重復(fù)獲取inputstream
chain.doFilter(new RepeatReadHttpRequest((HttpServletRequest) request), response);

關(guān)于利用springboot怎么實(shí)現(xiàn)一個(gè)代理分發(fā)服務(wù)功能就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到。

網(wǎng)站題目:利用springboot怎么實(shí)現(xiàn)一個(gè)代理分發(fā)服務(wù)功能-創(chuàng)新互聯(lián)
路徑分享:http://aaarwkj.com/article22/idocc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站建設(shè)、網(wǎng)頁(yè)設(shè)計(jì)公司、標(biāo)簽優(yōu)化、網(wǎng)站策劃自適應(yīng)網(wǎng)站、企業(yè)建站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

網(wǎng)站托管運(yùn)營(yíng)
不卡av免费在线网址| 婷婷五五月深爱开心激情| av天堂五月在线观看| 亚洲成人福利免费网站| 国产精品一区二区三区播放| 99国产精品热久久婷婷| 久久亚洲精品中文字幕馆| 91欧美在线激情视频| 中文字幕乱码在线观看一区| 国产91黑丝在线播放| 亚洲码欧洲码一二三区| 亚洲三级黄片免费播放| av国产一区二区在线| 国产熟女一区二区精品视频| 精品亚洲韩国一区二区三区| 台湾三级一区二区三区| 亚洲日本中文字幕免费观看| 亚洲三级伦理在线视频| 亚洲精品国产av成人| 精品人妻中文av一区二区| 国产亚洲精品视频中文字幕| 亚洲精品在线观看第一页| 日韩福利成人av在线| 日本中文字幕免费专区| 成人av在线播放亚洲| 亚洲精品网站国产高清| 91熟女激情五月综合| 亚洲成av人片青草影院| 少妇高潮一区二区三区在线| 无人区乱码一区二区三区| 亚洲精品成人午夜久久| 欧美一区二区精品少妇| 国产极品嫩模在线观看91| 亚洲成人高清av在线| 国内午夜福利精品视频| 国产91九色蝌蚪在线观看| 欧美三级黄片免费视频| 一区二区三区福利视频在线观看| 欧美日韩男女性生活视频| 欧美日韩一区二区三区色| 日韩免费在线观看av|