日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

自己动手写一个服务网关

發布時間:2025/3/21 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 自己动手写一个服务网关 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

什么是網關?為什么需要使用網關?

如圖所示,在不使用網關的情況下,我們的服務是直接暴露給服務調用方。當調用方增多,勢必需要添加定制化訪問權限、校驗等邏輯。當添加API網關后,在第三方調用端和服務提供方之間就創建了一面墻,這面墻直接與調用方通信進行權限控制。
本文所實現的網關源碼抄襲了---Oh,不對,是借鑒。借鑒了Zuul網關的源碼,提煉出其核心思路,實現了一套簡單的網關源碼,博主將其改名為Eatuul。

題外話

本文是業內能搜到的第一篇自己動手實現網關的文章。博主寫的手把手系列的文章,目的是在以最簡單的方式,揭露出中間件的核心原理,讓讀者能夠迅速了解實現的核心。需要說明的是,這不是源碼分析系列的文章,因此寫出來的代碼,省去了一些復雜的內容,畢竟大家能理解到該中間件的核心原理即可。如果想看源碼分析系列的,請關注博主,后期會將spring、spring boot、dubbo、mybatis等開源框架一一揭示。

正文設計思路

先大致說一下,就是定義一個Servlet接收請求。然后經過preFilter(封裝請求參數),routeFilter(轉發請求),postFilter(輸出內容)。三個過濾器之間,共享request、response以及其他的一些全局變量。如下圖所示

和真正的Zuul的區別?主要區別有如下幾點

  • Zuul中在異常處理模塊,有一個ErrorFilter來處理,博主在實現的時候偷懶了,略去。
  • Zuul中PreFilters,RoutingFilters,PostFilters默認都實現了一組,具體如下表所示
  • 博主總不可能每一個都給你們實現一遍吧。所以偷懶了,每種只實現一個。但是調用順序還是不變,按照PreFilters->RoutingFilters->PostFilters的順序調用

    在routeFilters確實有轉發請求的Filter,然而博主偷天換日了,改用RestTemplate實現.

    代碼結構

    大家去spring官網上搭建一套springboot的項目,博主就不展示pom的代碼了。直接將項目結構展示一下,如下圖所示


    EatuulServlet.java

    這個是網關的入口,邏輯也十分簡單,分為三步
    (1)將request,response放入threadlocal中
    (2)執行三組過濾器
    (3)清除threadlocal中的的環境變量


    源碼如下

    package?com.rjzheng.eatuul.http;import?java.io.IOException;import?javax.servlet.ServletException; import?javax.servlet.annotation.WebServlet; import?javax.servlet.http.HttpServlet; import?javax.servlet.http.HttpServletRequest; import?javax.servlet.http.HttpServletResponse;@WebServlet(name =?"eatuul", urlPatterns =?"/*") public?class?EatuulServlet?extends?HttpServlet?{private?EatRunner eatRunner =?new?EatRunner();@Overridepublic?void?service(HttpServletRequest req, HttpServletResponse resp)throws?ServletException, IOException?{//將request,和response放入上下文對象中eatRunner.init(req, resp);try?{//執行前置過濾eatRunner.preRoute();//執行過濾eatRunner.route();//執行后置過濾eatRunner.postRoute();}?catch?(Throwable e) {RequestContext.getCurrentContext().getResponse().sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());}?finally?{//清除變量RequestContext.getCurrentContext().unset();}}}

    EatuulRunner.java

    這個是具體的執行器。需要說明一下,在Zuul中,ZuulRunner在獲取具體有哪些過濾器的時候,有一個FileLoader可以動態讀取配置加載。博主在實現我們自己的EatuulRunner時候,略去動態讀取的過程,直接靜態寫死。

    源碼如下

    package?com.rjzheng.eatuul.http;import?java.util.ArrayList; import?java.util.List; import?java.util.concurrent.ConcurrentHashMap;import?javax.servlet.http.HttpServletRequest; import?javax.servlet.http.HttpServletResponse;import?com.rjzheng.eatuul.filter.EatuulFilter; import?com.rjzheng.eatuul.filter.post.SendResponseFilter; import?com.rjzheng.eatuul.filter.pre.RequestWrapperFilter; import?com.rjzheng.eatuul.filter.route.RoutingFilter;public?class?EatRunner?{//靜態寫死過濾器private?ConcurrentHashMap<String, List<EatuulFilter>> hashFiltersByType =?new?ConcurrentHashMap<String, List<EatuulFilter>>(){{ ?put("pre",new?ArrayList<EatuulFilter>(){{add(new?RequestWrapperFilter());}});put("route",new?ArrayList<EatuulFilter>(){{add(new?RoutingFilter());}});put("post",new?ArrayList<EatuulFilter>(){{add(new?SendResponseFilter());}});}};public?void?init(HttpServletRequest req, HttpServletResponse resp)?{RequestContext ctx = RequestContext.getCurrentContext();ctx.setRequest(req);ctx.setResponse(resp);}public?void?preRoute()?throws?Throwable?{runFilters("pre"); ?}public?void?route()?throws?Throwable{runFilters("route"); ? ?}public?void?postRoute()?throws?Throwable{runFilters("post");}public?void?runFilters(String sType)?throws?Throwable?{List<EatuulFilter> list =?this.hashFiltersByType.get(sType);if?(list !=?null) {for?(int?i =?0; i < list.size(); i++) {EatuulFilter zuulFilter = list.get(i);zuulFilter.run();}}} }

    EatuulFilter.java

    接下來就是一系列Filter的代碼了,先上父類EatuulFilter的源碼

    package?com.rjzheng.eatuul.filter;public?abstract?class?EatuulFilter?{abstract?public?String?filterType();abstract?public?int?filterOrder();abstract?public?void?run(); }

    RequestWrapperFilter.java

    這個是PreFilter,前置執行過濾器,負責封裝請求。步驟如下所示

    (1)封裝請求頭

    (2)封裝請求體

    (3)構造出RestTemplate能識別的RequestEntity

    (4)將RequestEntity放入全局threadlocal之中

    代碼如下所示

    package?com.rjzheng.eatuul.filter.pre;import?java.io.IOException; import?java.io.InputStream; import?java.net.URI; import?java.net.URISyntaxException; import?java.util.Collections; import?java.util.List;import?javax.servlet.http.HttpServletRequest;import?org.springframework.http.HttpHeaders; import?org.springframework.http.HttpMethod; import?org.springframework.http.RequestEntity; import?org.springframework.util.MultiValueMap; import?org.springframework.util.StreamUtils;import?com.rjzheng.eatuul.filter.EatuulFilter; import?com.rjzheng.eatuul.http.RequestContext;public?class?RequestWrapperFilter?extends?EatuulFilter{@Overridepublic?String?filterType()?{// TODO Auto-generated method stubreturn?"pre";}@Overridepublic?int?filterOrder()?{// TODO Auto-generated method stubreturn?-1;}@Overridepublic?void?run()?{String rootURL =?"http://localhost:9090";RequestContext ctx =RequestContext.getCurrentContext();HttpServletRequest servletRequest = ctx.getRequest();String targetURL = rootURL + servletRequest.getRequestURI();RequestEntity<byte[]> requestEntity =?null;try?{requestEntity = createRequestEntity(servletRequest, targetURL);}?catch?(Exception e) {e.printStackTrace();}//4、將requestEntity放入全局threadlocal之中ctx.setRequestEntity(requestEntity);}private?RequestEntity?createRequestEntity(HttpServletRequest request,String url)?throws?URISyntaxException, IOException?{String method = request.getMethod();HttpMethod httpMethod = HttpMethod.resolve(method);//1、封裝請求頭MultiValueMap<String, String> headers =createRequestHeaders(request);//2、封裝請求體byte[] body = createRequestBody(request);//3、構造出RestTemplate能識別的RequestEntityRequestEntity requestEntity =?new?RequestEntity<byte[]>(body,headers,httpMethod,?new?URI(url));return?requestEntity;}private?byte[] createRequestBody(HttpServletRequest request)?throws?IOException {InputStream inputStream = request.getInputStream();return?StreamUtils.copyToByteArray(inputStream);}private?MultiValueMap<String, String>?createRequestHeaders(HttpServletRequest request)?{HttpHeaders headers =?new?HttpHeaders();List<String> headerNames = Collections.list(request.getHeaderNames());for(String headerName:headerNames) {List<String> headerValues = Collections.list(request.getHeaders(headerName));for(String headerValue:headerValues) {headers.add(headerName, headerValue);}}return?headers;} }

    RoutingFilter.java

    這個是routeFilter,這里我偷懶了,直接做轉發請求,并且將返回值ResponseEntity放入全局threadlocal中

    package?com.rjzheng.eatuul.filter.route;import?org.springframework.http.RequestEntity; import?org.springframework.http.ResponseEntity; import?org.springframework.web.client.RestTemplate;import?com.rjzheng.eatuul.filter.EatuulFilter; import?com.rjzheng.eatuul.http.RequestContext;public?class?RoutingFilter?extends?EatuulFilter{@Overridepublic?String?filterType()?{// TODO Auto-generated method stubreturn?"route";}@Overridepublic?int?filterOrder()?{// TODO Auto-generated method stubreturn?0;}@Overridepublic?void?run(){RequestContext ctx = RequestContext.getCurrentContext();RequestEntity requestEntity = ctx.getRequestEntity();RestTemplate restTemplate =?new?RestTemplate();ResponseEntity responseEntity = restTemplate.exchange(requestEntity,byte[].class);ctx.setResponseEntity(responseEntity);}}

    SendResponseFilter.java

    這個是postFilters,將ResponseEntity輸出即可

    package?com.rjzheng.eatuul.filter.post;import?java.util.List; import?java.util.Map;import?javax.servlet.ServletOutputStream; import?javax.servlet.http.HttpServletResponse;import?org.springframework.http.HttpHeaders; import?org.springframework.http.ResponseEntity;import?com.rjzheng.eatuul.filter.EatuulFilter; import?com.rjzheng.eatuul.http.RequestContext;public?class?SendResponseFilter?extends?EatuulFilter{@Overridepublic?String?filterType()?{return?"post";}@Overridepublic?int?filterOrder()?{return?1000;}@Overridepublic?void?run()?{try?{addResponseHeaders();writeResponse();}?catch?(Exception e) {e.printStackTrace();}}private?void?addResponseHeaders()?{RequestContext ctx = RequestContext.getCurrentContext();HttpServletResponse servletResponse = ctx.getResponse();ResponseEntity responseEntity = ctx.getResponseEntity();HttpHeaders httpHeaders = responseEntity.getHeaders();for(Map.Entry<String, List<String>> entry:httpHeaders.entrySet()) {String headerName = entry.getKey();List<String> headerValues = entry.getValue();for(String headerValue:headerValues) {servletResponse.addHeader(headerName, headerValue);}}}private?void?writeResponse()throws?Exception?{RequestContext ctx = RequestContext.getCurrentContext();HttpServletResponse servletResponse = ctx.getResponse();if?(servletResponse.getCharacterEncoding() ==?null) {?// only set if not setservletResponse.setCharacterEncoding("UTF-8");}ResponseEntity responseEntity = ctx.getResponseEntity();if(responseEntity.hasBody()) {byte[] body = (byte[]) responseEntity.getBody();ServletOutputStream outputStream = servletResponse.getOutputStream();outputStream.write(body);outputStream.flush();}}}

    RequestContext.java

    最后是一直在說的全局threadlocal變量

    package?com.rjzheng.eatuul.http;import?java.util.HashMap; import?java.util.Map; import?java.util.concurrent.ConcurrentHashMap;import?javax.servlet.http.HttpServletRequest; import?javax.servlet.http.HttpServletResponse;import?org.springframework.http.RequestEntity; import?org.springframework.http.ResponseEntity;public?class?RequestContext?extends?ConcurrentHashMap<String,?Object>?{protected?static?Class<? extends RequestContext> contextClass = RequestContext.class;protected?static?final?ThreadLocal<? extends RequestContext> threadLocal =?new?ThreadLocal<RequestContext>() {@Overrideprotected?RequestContext?initialValue()?{try?{return?contextClass.newInstance();}?catch?(Throwable e) {throw?new?RuntimeException(e);}}};public?static?RequestContext?getCurrentContext()?{RequestContext context = threadLocal.get();return?context;}public?HttpServletRequest?getRequest()?{return?(HttpServletRequest) get("request");}public?void?setRequest(HttpServletRequest request)?{put("request", request);}public?HttpServletResponse?getResponse()?{return?(HttpServletResponse) get("response");}public?void?setResponse(HttpServletResponse response)?{set("response", response);}public?void?setRequestEntity(RequestEntity requestEntity){set("requestEntity",requestEntity);}public?RequestEntity?getRequestEntity()?{return?(RequestEntity) get("requestEntity");}public?void?setResponseEntity(ResponseEntity responseEntity){set("responseEntity",responseEntity);}public?ResponseEntity?getResponseEntity()?{return?(ResponseEntity) get("responseEntity");}public?void?set(String key, Object value)?{if?(value !=?null)put(key, value);elseremove(key);}public?void?unset()?{threadLocal.remove();}}

    如何測試?

    自己另外起一個server端口為9090如下所示

    package?com.rjzheng.eatservice;import?org.springframework.boot.autoconfigure.SpringBootApplication; import?org.springframework.boot.builder.SpringApplicationBuilder; import?org.springframework.boot.web.servlet.ServletComponentScan;import?com.rjzheng.eatservice.controller.IndexController;@SpringBootApplication @ServletComponentScan(basePackageClasses = IndexController.class) public?class?Application?{public?static?void?main(String[] args)?{new?SpringApplicationBuilder(Application.class).properties("server.port=9090").run(args);} }

    再來一個controller

    package?com.rjzheng.eatservice.controller;import?org.springframework.web.bind.annotation.RequestMapping; import?org.springframework.web.bind.annotation.RestController;@RestController public class IndexController {@RequestMapping("/index")public String index() {return?"hello!world";} }

    然后,你就發現可以從localhost:8080/index進行跳轉訪問了

    結論

    本文模擬了一下zuul網關的源碼,借鑒了一下其精髓的部分。希望大家能有所收獲

    ?

    出處:?http://rjzheng.cnblogs.com/

    作者:孤獨煙,作者公號,可以關注一波

    《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀

    總結

    以上是生活随笔為你收集整理的自己动手写一个服务网关的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 亚洲va中文字幕 | 久久综合婷婷国产二区高清 | 女人被狂躁60分钟视频 | 欧美久久久一区二区三区 | 深夜小视频在线观看 | 香蕉国产精品视频 | a黄色一级片 | 国产情侣露脸自拍 | 一级色网站 | 老熟妇一区二区三区啪啪 | 天堂中文在线观看视频 | 日韩在线播放视频 | 亚洲精品9999 | 欧美性生交xxxxx久久久缅北 | 久久久久久av无码免费网站 | 少妇无套内谢久久久久 | 豆花免费跳转入口官网 | 国产手机在线观看 | 在线看免费 | 国产人妖av| 99福利视频导航 | 性色视频在线观看 | 亚洲av无码乱码在线观看性色 | 亚洲AV无码精品国产 | 菠萝菠萝蜜网站 | 激情视频免费在线观看 | 国产激情图片 | 美女一区二区视频 | 国产麻豆自拍 | 人妻少妇精品一区二区三区 | 国产一二三在线 | 日日干天天干 | 人妻少妇偷人精品无码 | 成人欧美精品一区二区 | 欧美六区 | 欧美日韩123 | 动漫av网站| 农村寡妇一区二区三区 | 国产黄a三级三级看三级 | 嫩草网站入口 | 99re在线视频 | 好男人在线视频 | 亚洲精品中文在线 | 精品999久久久一级毛片 | 91九色在线 | 伊人激情综合网 | 黑森林av凹凸导航 | 在线欧美激情 | 欧美性生活精品 | 自拍偷拍欧美日韩 | jzz在线观看 | 91涩涩涩| 18视频在线观看娇喘 | 内射一区二区三区 | 逼特逼视频在线观看 | 一区二区三区爱爱 | 亚洲色图一区二区三区 | 中文字幕系列 | 成人亚洲网 | av在线资源网站 | 雨宫琴音一区二区三区 | 初高中福利视频网站 | 亚洲情射 | 青青草老司机 | www色| 日韩麻豆| 日韩免费毛片 | av免费观看在线 | 免费一级毛片麻豆精品 | 欧美视频免费看欧美视频 | 黄频在线免费观看 | 岛国大片在线观看 | 午夜av一区二区三区 | 日韩成人在线一区 | 成人颜色网站 | 丁香六月欧美 | 欧美日皮视频 | 亚洲人人夜夜澡人人爽 | 亚洲成人高清 | 91av免费看 | 国产视频分类 | 看黄色网址 | 男女草比视频 | 欧美激情亚洲激情 | 婷婷伊人五月天 | 欧美一二在线 | 色婷婷久久一区二区三区麻豆 | 亚洲影视一区二区 | 久久尤物视频 | 亚洲av久久久噜噜噜熟女软件 | 小蝌蚪视频色 | 99热中文 | 久久久久亚洲av无码麻豆 | 欧美成人三级视频 | 国产原创av在线 | 国产精品一区二区在线免费观看 | www.污网站| 亚洲 小说区 图片区 都市 | 美女免费黄色 |