在我们的rest服务中,需要暴露一个中间件的接口给用户,但是需要经过rest服务的认证,这是典型的网关使用场景。可以引入网关组件来搞定,但是引入zuul等中间件会增加系统复杂性,这里实现一个超轻量级的网关,只实现请求转发,认证等由rest服务的spring security来搞定。
如何进行请求转发呢? 熟悉网络请求的同学应该很清楚,请求无非就是请求方式、HTTP header,以及请求body,我们将这些信息取出来,透传给转发的url即可。
举例:
/graphdb/** 转发到 Graph_Server/**
获取转发目的地址:#
Copy
private String createRedictUrl(HttpServletRequest request, String routeUrl, String prefix) {
String queryString = request.getQueryString();
return routeUrl + request.getRequestURI().replace(prefix, "") +
(queryString != null ? "?" + queryString : "");
}
解析请求头和内容#
然后从request中提取出header、body等内容,构造一个RequestEntity
,后续可以用RestTemplate
来请求。
Copyprivate RequestEntity createRequestEntity(HttpServletRequest request, String url) throws URISyntaxException, IOException { String method = request.getMethod(); HttpMethod httpMethod = HttpMethod.resolve(method); MultiValueMap<String, String> headers = parseRequestHeader(request); byte[] body = parseRequestBody(request); return new RequestEntity<>(body, headers, httpMethod, new URI(url)); }
<span class="hljs-keyword">private</span> <span class="hljs-keyword">byte</span>[] parseRequestBody(HttpServletRequest request) <span class="hljs-keyword">throws</span> IOException { InputStream inputStream = request.getInputStream(); <span class="hljs-keyword">return</span> StreamUtils.copyToByteArray(inputStream); } <span class="hljs-function"><span class="hljs-keyword">private</span> MultiValueMap<String, String> <span class="hljs-title">parseRequestHeader</span><span class="hljs-params">(HttpServletRequest request)</span> </span>{ HttpHeaders headers = <span class="hljs-keyword">new</span> HttpHeaders(); List<String> headerNames = Collections.list(request.getHeaderNames()); <span class="hljs-keyword">for</span> (String headerName : headerNames) { List<String> headerValues = Collections.list(request.getHeaders(headerName)); <span class="hljs-keyword">for</span> (String headerValue : headerValues) { headers.add(headerName, headerValue); } } <span class="hljs-keyword">return</span> headers; }
透明转发#
最后用RestTemplate
来实现请求:
Copy
private ResponseEntity<String> route(RequestEntity requestEntity) {
RestTemplate restTemplate = new RestTemplate();
return restTemplate.exchange(requestEntity, String.class);
}
全部代码#
以下是轻量级转发全部代码:
Copy
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StreamUtils;
import org.springframework.web.client.RestTemplate;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.List;
@Service
public class RoutingDelegate {
<span class="hljs-function"><span class="hljs-keyword">public</span> ResponseEntity<String> <span class="hljs-title">redirect</span><span class="hljs-params">(HttpServletRequest request, HttpServletResponse response,String routeUrl, String prefix)</span> </span>{
<span class="hljs-keyword">try</span> {
<span class="hljs-comment">// build up the redirect URL</span>
String redirectUrl = createRedictUrl(request,routeUrl, prefix);
RequestEntity requestEntity = createRequestEntity(request, redirectUrl);
<span class="hljs-keyword">return</span> route(requestEntity);
} <span class="hljs-keyword">catch</span> (Exception e) {
<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ResponseEntity(<span class="hljs-string">"REDIRECT ERROR"</span>, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
<span class="hljs-function"><span class="hljs-keyword">private</span> String <span class="hljs-title">createRedictUrl</span><span class="hljs-params">(HttpServletRequest request, String routeUrl, String prefix)</span> </span>{
String queryString = request.getQueryString();
<span class="hljs-keyword">return</span> routeUrl + request.getRequestURI().replace(prefix, <span class="hljs-string">""</span>) +
(queryString != <span class="hljs-keyword">null</span> ? <span class="hljs-string">"?"</span> + queryString : <span class="hljs-string">""</span>);
}
<span class="hljs-function"><span class="hljs-keyword">private</span> RequestEntity <span class="hljs-title">createRequestEntity</span><span class="hljs-params">(HttpServletRequest request, String url)</span> <span class="hljs-keyword">throws</span> URISyntaxException, IOException </span>{
String method = request.getMethod();
HttpMethod httpMethod = HttpMethod.resolve(method);
MultiValueMap<String, String> headers = parseRequestHeader(request);
<span class="hljs-keyword">byte</span>[] body = parseRequestBody(request);
<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> RequestEntity<>(body, headers, httpMethod, <span class="hljs-keyword">new</span> URI(url));
}
<span class="hljs-function"><span class="hljs-keyword">private</span> ResponseEntity<String> <span class="hljs-title">route</span><span class="hljs-params">(RequestEntity requestEntity)</span> </span>{
RestTemplate restTemplate = <span class="hljs-keyword">new</span> RestTemplate();
<span class="hljs-keyword">return</span> restTemplate.exchange(requestEntity, String.class);
}
<span class="hljs-keyword">private</span> <span class="hljs-keyword">byte</span>[] parseRequestBody(HttpServletRequest request) <span class="hljs-keyword">throws</span> IOException {
InputStream inputStream = request.getInputStream();
<span class="hljs-keyword">return</span> StreamUtils.copyToByteArray(inputStream);
}
<span class="hljs-function"><span class="hljs-keyword">private</span> MultiValueMap<String, String> <span class="hljs-title">parseRequestHeader</span><span class="hljs-params">(HttpServletRequest request)</span> </span>{
HttpHeaders headers = <span class="hljs-keyword">new</span> HttpHeaders();
List<String> headerNames = Collections.list(request.getHeaderNames());
<span class="hljs-keyword">for</span> (String headerName : headerNames) {
List<String> headerValues = Collections.list(request.getHeaders(headerName));
<span class="hljs-keyword">for</span> (String headerValue : headerValues) {
headers.add(headerName, headerValue);
}
}
<span class="hljs-keyword">return</span> headers;
}
}
Spring 集成#
Spring Controller,RequestMapping里把GET \ POST\PUT\DELETE 支持的请求带上,就能实现转发了。
Copy@RestController @RequestMapping(GraphDBController.DELEGATE_PREFIX) @Api(value = "GraphDB", tags = { "graphdb-Api" }) public class GraphDBController {
<span class="hljs-meta">@Autowired</span> GraphProperties graphProperties; <span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">static</span> String DELEGATE_PREFIX = <span class="hljs-string">"/graphdb"</span>; <span class="hljs-meta">@Autowired</span> <span class="hljs-keyword">private</span> RoutingDelegate routingDelegate; <span class="hljs-meta">@RequestMapping(value = "/**", method = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE}, produces = MediaType.TEXT_PLAIN_VALUE)</span> <span class="hljs-function"><span class="hljs-keyword">public</span> ResponseEntity <span class="hljs-title">catchAll</span><span class="hljs-params">(HttpServletRequest request, HttpServletResponse response)</span> </span>{ <span class="hljs-keyword">return</span> routingDelegate.redirect(request, response, graphProperties.getGraphServer(), DELEGATE_PREFIX); }
}