1 背景
在目前的项目开发过程中,通常采用前后端分离的架构并行开发。而前后端开发通常项目也是分开部署实现的。也就是前端和后端是由两个服务器分开部署,而后端通常也是多模块并行开发的,这就会造成前端项目无法访问后端项目的接口。这时,如果后端能够实现路由转发的话,就可以实现后端多个模块之间相互调用,从而解决上面出现的问题。
2 基本思路
由于后端各个模块相互独立,所以无法修改后端模块实现。通常我们会采用在前端项目中新增一个路由中转器,实现通常url的不同进行跳转到不同的项目中,从而访问到后端的各个接口。
实现方式:URL(跳转信息)+后端路由转发
3 代码实现
3.1 拦截controller
@Controller
@RequestMapping(value = "/user/redirect")
public class RedirectController {
//在user项目中关于其它子项目比如trade项目的jsp页面
//ajax请求需要加上前缀/user/redirect
private static int offset = "/user/redirect/".length();
private static Logger logger = Logger.getLogger(RedirectController.class);
@ApiOperation(value = "重定向", hidden = true)
@RequestMapping(value = "/**", method = {RequestMethod.POST, RequestMethod.GET})
public void redirect(HttpServletRequest request, HttpServletResponse response) throws Exception {
String url = request.getRequestURI();
int base = request.getContextPath().length();
String host = url.substring(base + offset, url.indexOf("/",base + offset));//例如: trade
logger.info("request.getRequestURI():"+request.getRequestURI()+"\tbase:"+base+"\thost:"+host);
//host url request response additionalMap
HttpUtil.httpRequest(host, url.substring(url.indexOf("/",base + offset)),//例如: /allProduction/getAllProductionPage
request, response, new ArrayList<NameValuePair>());
response.getOutputStream().flush();
response.getOutputStream().close();
}
}
3.2 HttpUtil
package edu.whut.imgProcess.redirect;
import org.apache.http.*;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
/**
* com.whut.athena.util
* Created by YTY on 2016/4/5.
*/
public class HttpUtil {
private static Logger logger = Logger.getLogger(HttpUtil.class);
public static void httpRequest(String hostPrefix, String url, HttpServletRequest request, HttpServletResponse response,
List<NameValuePair> additionalMap) {
CloseableHttpClient httpClient = SSLUtils.createSSLClientDefault();
HttpPost httpPost = new HttpPost(RedirectUtil.getHost(hostPrefix, url));
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
if (headerName.startsWith("accept") || headerName.startsWith("cookie")) {
httpPost.addHeader(headerName, request.getHeader(headerName));
}
}
List<NameValuePair> params = new ArrayList<NameValuePair>();
Enumeration<String> parameterNames = request.getParameterNames();
while (parameterNames.hasMoreElements()) {
String parameterName = parameterNames.nextElement();
for (String value : request.getParameterValues(parameterName)) {
if (value!=null&&!value.equals("null")) params.add(new BasicNameValuePair(parameterName, value));
//logger.info(parameterName + ":" + value);
}
}
params.addAll(additionalMap);
for (NameValuePair map : additionalMap) {
//logger.info(map.getName() + ":" + map.getValue());
}
HttpResponse httpResponse = null;
try {
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, RedirectUtil.SERVER_CHARSET);
httpPost.setEntity(entity);
httpResponse = httpClient.execute(httpPost);
if (httpResponse != null) {
HttpEntity responseEntity = httpResponse.getEntity();
if (responseEntity != null) {
//logger.info(responseEntity.toString());
responseEntity.writeTo(response.getOutputStream());
}
}
} catch (IOException e) {
e.printStackTrace();
}
if (httpResponse != null) {
response.setStatus(httpResponse.getStatusLine().getStatusCode());
//logger.info(httpResponse.toString());
HeaderIterator headerIterator = httpResponse.headerIterator();
while (headerIterator.hasNext()) {
Header header = headerIterator.nextHeader();
if (header.getName().equals("Content-Type")) {
//response.addHeader(header.getName(), header.getValue());
response.setHeader(header.getName(), header.getValue());//或许可以解决重定向乱码(好像没影响)
}
}
response.setHeader("Server", "nginx");
}
}
public static String httpRequest(String hostPrefix, String url, List<NameValuePair> params) {
CloseableHttpClient httpClient = SSLUtils.createSSLClientDefault();
logger.info(url);
HttpPost httpPost = new HttpPost(RedirectUtil.getHost(hostPrefix, url));
try {
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, RedirectUtil.SERVER_CHARSET);
httpPost.setEntity(entity);
HttpResponse httpResponse = httpClient.execute(httpPost);
if (httpResponse != null) {
HttpEntity responseEntity = httpResponse.getEntity();
if (responseEntity != null) {
String result = EntityUtils.toString(responseEntity, RedirectUtil.SERVER_CHARSET);
logger.info(result);
return result;
}
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
3.3 重定向工具类(本地和服务器能不同场景自动切换)
public class RedirectUtil {
public static boolean DEBUG;
public static String getServerHost() {
return SERVER_HOST;
}
static String SERVER_HOST;
static int SERVER_PORT;
static String SERVER_PROTOCOL;
static String SERVER_CHARSET;
static Map<String, String> hostPrefixMap;
static {
ResourceBundle bundle = ResourceBundle.getBundle("server");
SERVER_HOST = bundle.getString("host");
SERVER_PORT = Integer.parseInt(bundle.getString("port"));
SERVER_PROTOCOL = bundle.getString("protocol");
SERVER_CHARSET = bundle.getString("charset");
DEBUG = SERVER_HOST.equals("localhost");
hostPrefixMap = new HashMap<String, String>();
hostPrefixMap.put("user", bundle.getString("user"));
hostPrefixMap.put("algorithm", bundle.getString("algorithm"));
hostPrefixMap.put("image", bundle.getString("image"));
}
//获取是否为本地调试
public static Boolean getDebug(){
return DEBUG;
}
public static String getHost(String hostPrefix) {
return getHost(hostPrefix, "");
}
public static String getHost(String hostPrefix, String url) {
hostPrefix = hostPrefixMap.get(hostPrefix);//例如: trade
String host = SERVER_HOST;
if (hostPrefix != null && !hostPrefix.isEmpty()) {
if (DEBUG) {
//如果是user的话就不需要转发
//DEBUG代表本地只需要改变url前缀即可
url = "/" + hostPrefix + url;//例如: / + image + /image/report/getReportList
} else {
//host = hostPrefix + "." + host;//例如: app + . + ceks100.com
if(hostPrefix.equals("user")){
//服务器上的话改变host即可,不需要改变url前缀
host = "td9faceusers.d9lab.net";//例如: user服务器
}
else if(hostPrefix.equals("algorithm")){
host = "td9facealgorithm.d9lab.net";
}
else if(hostPrefix.equals("image")){
host = "td9faceimages.d9lab.net";
}
}
}
try {
if(DEBUG){
return new URL(SERVER_PROTOCOL, host, SERVER_PORT, url).toString();
}
else {
return new URL(SERVER_PROTOCOL, host, url).toString(); //服务器上不要Port
}
} catch (MalformedURLException e) {
e.printStackTrace();
return SERVER_PROTOCOL + "://" + host + ":" + SERVER_PORT + "url";
}
}
}
3.4 SSLUtils 工具类
/**
* Created by #{QiuYW }on 2017/1/14.
* SSLContextBuilder在4.3版本后被作者移到org.apache.http.ssl包里去了
*/
public class SSLUtils {
private static Logger logger = Logger.getLogger(SSLUtils.class);
public static CloseableHttpClient createSSLClientDefault() {
try {
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
// 信任所有
@Override
public boolean isTrusted(X509Certificate[] chain, String authType) {
return true;
}
}).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);
return HttpClients.custom().setSSLSocketFactory(sslsf).build();
} catch (KeyManagementException e) {
logger.error("SSLUtilsErrorKetManage");
} catch (NoSuchAlgorithmException e) {
logger.error("SSLUtilsErrorNOAlgorithm");
} catch (KeyStoreException e) {
logger.error("SSLUtilsErrorKeyStore");
}
return HttpClients.createDefault();
}
}
4 总结
通过后端路由转发,可以实现后端多个模块并行开发,同时也能独立部署,大大提高了开发的效率。