The preface

You don’t even know the external interface signature? I still have time to learn.

Many partners respond that the related signing and checking of external APIs can be handled by the gateway.

Speaking of gateways, we must be familiar with them. Spring Cloud/Kong/Soul are widely used in the market.

The role of the API gateway

(1) Permission verification in the external interface

(2) Restrictions on the number and frequency of port calls

(3) load balancing, caching, routing, access control, service proxy, monitoring, logging, etc.

Realize the principle of

In general, the request directly accesses the server side through the client. We need to implement a layer of API gateway in the middle. The external client accesses the Gateway, and then the Gateway forwards the call.

The core processes

Gateway sounds very complicated, the core part is actually based on the Servlet javax.servlet.filter implementation.

We ask the client to call the gateway, and then parse and forward the message problem in the Filter, and then encapsulate and return it to the client after calling the server.

import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; /** * @Author binbin.hou * @Since 1.0.0 */ @WebFilter @Component public class GatewayFilter implements Filter {private static final Logger LOGGER = LoggerFactory.getLogger(GatewayFilter.class); public void init(FilterConfig filterConfig) throws ServletException { } public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) servletRequest; LOGGER.info("url={}, params={}", req.getRequestURI(), JSON.toJSONString(req.getParameterMap())); // TODO... // TODO... // TODO... } else { filterChain.doFilter(req, servletResponse); } } public void destroy() { } }

Next, we just need to focus on overriding the doFilter method.

The specific implementation

Get appName

The gateway is open to all applications within the company, and we can distinguish it by the unique AppName of each service.

For example, if the application name is test, then the gateway request is invoked:

https://gateway.com/test/version

For this request, the corresponding appName is test, and the actual request URL is /version.

The concrete implementation is also relatively simple:

@Override public Pair<String, String> getRequestPair(HttpServletRequest req) { final String url = req.getRequestURI(); if(url.startsWith("/") && url.length() > 1) { String subUrl = url.substring(1); int nextSlash = subUrl.indexOf("/"); If (nextSlash < 0) {logger.warn (" Request address {} corresponding AppName does not exist." , url); return Pair.of(null, null); } String appName = subUrl.substring(0, nextSlash); String realUrl = subUrl.substring(nextSlash); Logger.info (" request address {} corresponding appName: {}, real request address: {}", URL, appName, realURL); return Pair.of(appName, realUrl); } logger.warn (" Request address: {} does not start with /, or if the length is less than 2, ignore it." , url); return Pair.of(null, null); }

Request header

Construct the corresponding request header from HttpServletRequest:

/** * build map information * @param req request * @return result * @since 1.0.0 */ private map <String, String> buildHeaderMap(final HttpServletRequest req) { Map<String, String> map = new HashMap<>(); Enumeration<String> enumeration = req.getHeaderNames(); while (enumeration.hasMoreElements()) { String name = enumeration.nextElement(); String value = req.getHeader(name); map.put(name, value); } return map; }

Service discovery

When appName = test, we can query the IP :port information of the test application in the configuration center.

@Override public String buildRequestUrl(Pair<String, String> pair) { String appName = pair.getValueOne(); String appUrl = pair.getValueTwo(); String ipPort = "127.0.0.1:8081"; If (appname.equals ("test")) {// Load balancing IPPort = "127.0.0.1:8081"; } else { throw new GatewayServerException(GatewayServerRespCode.APP_NAME_NOT_FOUND_IP); } String format = "http://%s/%s"; return String.format(format, ipPort, appUrl); }

This is fixed for the time being, and finally returns the actual server request address.

Further server selection can also be made in conjunction with specific load balancing/routing strategies.

Different Method

HTTP supports a variety of methods, we will support GET/POST requests for the time being.

This is essentially a build form request that invokes the server for a GET/POST request.

This can be done in a variety of ways, using the OK-HTTP client as an example.

The interface definition

To facilitate further expansion, all Method calls implement the same interface:

Public interface ImethodType {/** * @param context * @return result */ ImethodtyPereResult handle(final) IMethodTypeContext context); }

GET

A GET request.

@Service @MethodTypeRoute("GET") public class GetMethodType implements IMethodType { @Override public IMethodTypeResult handle(IMethodTypeContext context) { String respBody = OkHttpUtil.get(context.url(), context.headerMap()); return MethodTypeResult.newInstance().respJson(respBody); }}

POST

A POST request.

@Service @MethodTypeRoute("POST") public class PostMethodType implements IMethodType { @Override public IMethodTypeResult handle(IMethodTypeContext context) { HttpServletRequest req = (HttpServletRequest) context.servletRequest(); String postJson = HttpUtil.getPostBody(req); String respBody = OkHttpUtil.post(context.url(), postJson, context.headerMap()); return MethodTypeResult.newInstance().respJson(respBody); }}

OkHttpUtil implementation

OkHttpUtil is an HTTP invocation utility class based on the OK-HTTP wrapper.

import com.github.houbb.gateway.server.util.exception.GatewayServerException; import com.github.houbb.heaven.util.util.MapUtil; import okhttp3.*; import java.io.IOException; import java.util.Map; /** * @Author binbin.hou * @Since 1.0.0 */ public class OKHttpUtil {private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); Public static String get(final String url) {return return (url); public static String get(final String url) {return return (url);  null); } /** * get request * @Param URL * @Param headerMap request header * @return result * @since 1.0.0 */ public static String get(final) String url, final Map<String, String> headerMap) { try { OkHttpClient client = new OkHttpClient(); Request.Builder builder = new Request.Builder(); builder.url(url); if(MapUtil.isNotEmpty(headerMap)) { for(Map.Entry<String, String> entry : headerMap.entrySet()) { builder.header(entry.getKey(), entry.getValue()); } } Request request = builder .build(); Response response = client.newCall(request).execute(); return response.body().string(); } catch (IOException e) { throw new GatewayServerException(e); }} /** * GET request * @Param URL * @Param body * @Param headerMap request header * @return result * @since 1.0.0 */ public static String post(final String url, final RequestBody body, final Map<String, String> headerMap) { try { OkHttpClient client = new OkHttpClient(); Request.Builder builder = new Request.Builder(); builder.post(body); builder.url(url); if(MapUtil.isNotEmpty(headerMap)) { for(Map.Entry<String, String> entry : headerMap.entrySet()) { builder.header(entry.getKey(), entry.getValue()); } } Request request = builder.build(); Response response = client.newCall(request).execute(); return response.body().string(); } catch (IOException e) { throw new GatewayServerException(e); }} /** * Get request * @Param URL * @Param bodyJSON request bodyJson * @Param headerMap request header * @Return result * @Since 1.0.0 */ public  static String post(final String url, final String bodyJson, final Map<String, String> headerMap) { RequestBody body = RequestBody.create(JSON, bodyJson); return post(url, body, headerMap); }}

Call result processing

After the request to the server, we need to process the results.

The first version of the implementation was very rough:

/** * Handle the final result * @Param MethodTypeResult * @Param ServletResponse response * @Since 1.0.0 */ Private void methodTypeResultHandle(final IMethodTypeResult methodTypeResult, final ServletResponse servletResponse) { try { final String respBody = methodTypeResult.respJson(); // Redirect (This solution should be deprecated for network security reasons.) // This can be redirected or the request can be made via the HTTP Client. / / GET/POST / / retrieve character servletResponse output stream object. SetCharacterEncoding (" utf-8 "); servletResponse.setContentType("text/html; charset=utf-8"); servletResponse.getWriter().write(respBody); } catch (IOException e) { throw new GatewayServerException(e); }}

Complete implementation

We put the above main processes together, and the complete implementation is as follows:

import com.alibaba.fastjson.JSON; import com.github.houbb.gateway.server.util.exception.GatewayServerException; import com.github.houbb.gateway.server.web.biz.IRequestAppBiz; import com.github.houbb.gateway.server.web.method.IMethodType; import com.github.houbb.gateway.server.web.method.IMethodTypeContext; import com.github.houbb.gateway.server.web.method.IMethodTypeResult; import com.github.houbb.gateway.server.web.method.impl.MethodHandlerContainer; import com.github.houbb.gateway.server.web.method.impl.MethodTypeContext; import com.github.houbb.heaven.support.tuple.impl.Pair; import com.github.houbb.heaven.util.lang.StringUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; /** * @Author binbin.hou * @Since 1.0.0 */ @WebFilter @Component public class GatewayFilter implements Filter { private static final Logger LOGGER = LoggerFactory.getLogger(GatewayFilter.class); @Autowired private IRequestAppBiz requestAppBiz; @Autowired private MethodHandlerContainer methodHandlerContainer; public void init(FilterConfig filterConfig) throws ServletException { } public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) servletRequest; LOGGER.info("url={}, params={}", req.getRequestURI(), JSON.toJSONString(req.getParameterMap())); / / based on the URL to obtain the corresponding service name Pair < String, the String > Pair. = requestAppBiz getRequestPair (the req); Map<String, String> headerMap = buildHeaderMap(req); String appName = pair.getValueOne(); if(StringUtil.isNotEmptyTrim(appName)) { String method = req.getMethod(); String respBody = null; String url = requestAppBiz.buildRequestUrl(pair); / / TODO: the support of other methods IMethodType methodType. = methodHandlerContainer getMethodType (method); IMethodTypeContext typeContext = MethodTypeContext.newInstance() .methodType(method) .url(url) .servletRequest(servletRequest) .servletResponse(servletResponse) .headerMap(headerMap); // IMethodPereResult = MethodType.handle (TypeContext); / / / / results after execution of processing this. MethodTypeResultHandle (methodTypeResult, servletResponse); } else { filterChain.doFilter(req, servletResponse); }} public void destroy() {} /** * Handle the final result * @Param MethodTyleResult * @Param ServletResponse * @Since 1.0.0 */ private void methodTypeResultHandle(final IMethodTypeResult methodTypeResult, final ServletResponse servletResponse) { try { final String respBody = methodTypeResult.respJson(); // Redirect (This solution should be deprecated for network security reasons.) // This can be redirected or the request can be made via the HTTP Client. / / GET/POST / / retrieve character servletResponse output stream object. SetCharacterEncoding (" utf-8 "); servletResponse.setContentType("text/html; charset=utf-8"); servletResponse.getWriter().write(respBody); } catch (IOException e) { throw new GatewayServerException(e); }} /** * build map information * @param req request * @return result * @since 1.0.0 */ private map <String, String> buildHeaderMap(final HttpServletRequest req) { Map<String, String> map = new HashMap<>(); Enumeration<String> enumeration = req.getHeaderNames(); while (enumeration.hasMoreElements()) { String name = enumeration.nextElement(); String value = req.getHeader(name); map.put(name, value); } return map; }}

The gateway validation

The gateway application

After we add the interceptor, we define the corresponding Application as follows:

@SpringBootApplication @ServletComponentScan public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}

Then start the gateway with the port number 8080

Server application

Then start the service corresponding to the server, the port number is 8081.

View the version number of the controller implementation:

@RestController public class MonitorController { @RequestMapping(value = "version", Method = requestMethod.get) public String version() {return "1.0-demo"; }}

request

We access the API gateway directly from the browser:

http://localhost:8080/test/version

Page returned:

1.0 the demo

summary

The principle of API gateway implementation is not difficult, is based on the servlet to forward the request.

While this may seem simple, you can build on this to implement more powerful features such as stream-limiting, logging, monitoring, and so on.

If you are interested in the API gateway, you may wish to pay attention to the next wave of content, even more exciting.

Note: There is a lot of code involved, which is simplified in the article. If you are interested in all the source code, you can pay attention to the [old horse loud west wind], the background reply [gateway] can be obtained.

I am an old horse, looking forward to the next meeting with you.