Vấn đề gặp phải
Ví dụ về proxy từ /amp đến /crm
Địa chỉ yêu cầu:
Thông tin in ra (đã bỏ qua phần giải quyết dấu gạch chéo bị trùng)
Có thể thấy contextPath là /crm của Tomcat thay vì /amp được yêu cầu bởi trình duyệt. Điều này có thể dẫn đến:
- Lỗi 404 do công cụ template (JSP, Thymeleaf)拼接 sai thông tin header
- Lỗi 404 khi thực hiện Redirect từ phía backend
Giải pháp
====
Tạo lớp kế thừa HttpServletRequestWrapper, ghi đè các phương thức getContextPath, getRequestURI, getRequestURL, getScheme, getServerPort (hai phương thức sau để giải quyết vấn đề khi Nginx proxy qua HTTPS)
Cấu hình Nginx
location /amp {
proxy_pass http://127.0.0.1:8081/crm/;
proxy_set_header Host $host;
proxy_set_header Port $server_port; # để sử dụng trong code Java
proxy_set_header Request-URI $request_uri; # để sử dụng trong code Java
proxy_set_header Context-Path /amp; # để sử dụng trong code Java
proxy_cookie_path /crm /amp;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; # để sử dụng trong code Java
}
Lớ HttpServletRequestWrapper tùy chỉnh
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.Map;
/**
* Lớp wrapper để sửa chữa contextPath sau khi được Nginx chuyển tiếp
*/
public class CustomRequestWrapper extends HttpServletRequestWrapper {
private final String originalContextPath;
private final String originalRequestURI;
public CustomRequestWrapper(HttpServletRequest request) {
super(request);
this.originalContextPath = request.getContextPath();
this.originalRequestURI = request.getRequestURI();
}
@Override
public String getContextPath() {
String unknown = "unknown";
// Ưu tiên lấy giá trị từ header được Nginx thiết lập
String contextPath = super.getContextPath();
String temp = getHeader("Context-Path");
if (temp != null && !temp.isEmpty() && !unknown.equalsIgnoreCase(temp)) {
contextPath = temp;
}
return contextPath;
}
@Override
public String getRequestURI() {
// Thay thế phần oldContextPath bằng contextPath mới
String requestURI = super.getRequestURI();
if (requestURI.startsWith(originalContextPath)) {
requestURI = getContextPath() + requestURI.substring(originalContextPath.length());
}
return requestURI;
}
@Override
public StringBuffer getRequestURL() {
// Thay thế phần oldRequestURI trong RequestURL bằng URI mới
StringBuffer requestURL = super.getRequestURL();
String urlStr = requestURL.toString();
if (urlStr.endsWith(originalRequestURI)) {
requestURL.replace(
urlStr.indexOf(originalRequestURI),
urlStr.indexOf(originalRequestURI) + originalRequestURI.length(),
getRequestURI());
}
return requestURL;
}
@Override
public String getScheme() {
String unknown = "unknown";
// Ưu tiên lấy giá trị từ header được Nginx thiết lập
String scheme = super.getScheme();
String temp = getHeader("X-Forwarded-Proto");
if (temp != null && !temp.isEmpty() && !unknown.equalsIgnoreCase(temp)) {
scheme = temp;
}
return scheme;
}
@Override
public int getServerPort() {
String unknown = "unknown";
// Ưu tiên lấy giá trị từ header được Nginx thiết lập
int serverPort = super.getServerPort();
String temp = getHeader("Port");
if (temp != null && !temp.isEmpty() && !unknown.equalsIgnoreCase(temp)) {
serverPort = Integer.parseInt(temp);
}
return serverPort;
}
}
Tạo Filter để áp dụng wrapper cho request
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* Filter để thay thế request object khi sử dụng Nginx làm reverse proxy
*/
@Component
public class NginxProxyFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
CustomRequestWrapper customRequest = new CustomRequestWrapper((HttpServletRequest) request);
chain.doFilter(customRequest, response);
}
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void destroy() {
}
}