手写HTTP代理服务器,使用灵活
手写HTTP代理转发工具--不需要对系统设置,应用层代理
- 本文地址:https://www.cnblogs.com/muphy/p/16405439.html
- 手写通过系统设置的代理:Java实现http代理服务器
背景
代理工具实际上有很多,最常用的就是ngnix了,能代理非常多的场景,但是在生产开发验证的时候,遇到了一个问题,某些平台的回调地址必须使用域名才能访问,当回调地址使用公网IP又无权限设置域名解析的时候,只有使用ngnix做代理转发,在ngnix中设置proxy路径的代理,配置为:
location /proxy/ {
proxy_redirect off;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://公网IP:8888/test/;
}
但是访问代理后的路径又出现一个了问题,访问地址出现如下图所示的限制:
于是自己手写代理绕过这个限制
原理
如需要访问的Tomcat的路径为:http://公网IP:8888/test/
,原始Get请求资源路径为:http://公网IP:8888/test/index.html?name=hello
,其中公网IP可能是阿里云、腾讯云等,域名访问会被要求备案
1.设置代理
经过url编码后作为参数传递: GET /proxy/setProxy?url=http%3A%2F%2F公网IP%3A8888%2Ftest%2F&content=tomcat
请求会将tomcat的代理加入proxyContainer容器,可以设置多个哦
2.发起请求
请求当前路径地址:GET /proxy/tomcat/index.html?name=hello
,就会后台自动请求http://公网IP:8888/test/index.html?name=hello
,并将结果返回给客户端
源码实现
ProxyController.java
//package me.muphy;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;
/**
* 手写代理转发工具
* 如Tomcat的路径为:http://127.0.0.1:8888/test/,Get请求资源路径为:http://127.0.0.1:8888/test/user/page?page=1&pageSize=10
* 1.通过请求设置代理
* 经过url编码后,调用: GET /proxy/setProxy?url=http%3A%2F%2F127.0.0.1%3A8888%2Ftest%2F&content=tomcat&remove=true
* 2.发起请求
* 请求当前路径地址:GET /proxy/tomcat/user/page?page=1&pageSize=10,就会后台自动请求http://127.0.0.1:8888/test/user/page?page=1&pageSize=10,并将结果返回给客户端
*
* @author gzhx
* @date 2022-02-14
*/
@RestController
@RequestMapping
public class ProxyController {
/**
* 代理容器
*/
private static final List<ProxyMapping> proxyContainer = new ArrayList<>();
/**
* 代理映射
*/
public static class ProxyMapping {
private String content;
private String url;
/**
* 代理后的路径是否是否包含content,默认不包含
*/
private Boolean remove = false;
public ProxyMapping(String content, String url) {
this.content = content;
this.url = url;
}
public ProxyMapping(String content, String url, Boolean remove) {
this(content, url);
if (remove != null) {
this.remove = remove;
}
}
public String getContent() {
return content;
}
public String getUrl() {
return url;
}
public Boolean getRemove() {
if (StringUtils.isEmpty(content)) {
return false;
}
return remove;
}
public void setContent(String content) {
this.content = content;
}
public void setUrl(String url) {
this.url = url;
}
public void setRemove(Boolean remove) {
if (remove != null) {
this.remove = remove;
}
}
@Override
public String toString() {
return "ProxyMapping{" +
"content='" + content + '\'' +
", url='" + url + '\'' +
", remove=" + remove +
'}';
}
}
/**
* 转发GET请求
*
* @author 子安
* @date 2022-05-01
*/
@GetMapping("/**")
public Object proxyGet(HttpServletRequest request, HttpServletResponse response, @RequestParam Map<String, Object> map) throws UnsupportedEncodingException {
String url = getProxyUrl(request, map);
System.out.println("url:" + url);
if (url == null) {
return "404 " + request.getRequestURI();
}
RestTemplate restTemplate = getRestTemplate(request);
ResponseEntity<byte[]> forObject = restTemplate.postForEntity(url, body, byte[].class);
setResponseHeader(response, forObject);
//String forObject = HttpUtil.get(url, map, 30000);
System.out.println("result:" + forObject);
return forObject;
}
/**
* 转发POST代理
* 如: http://localhost:8080/proxy/
*
* @author 子安
* @date 2022-05-01
*/
@PostMapping("/**")
public Object proxyPost(HttpServletRequest request, HttpServletResponse response, @RequestParam Map<String, Object> map, @RequestBody String body) throws UnsupportedEncodingException {
String url = getProxyUrl(request, map);
System.out.println("url:" + url);
if (url == null) {
return "404 " + request.getRequestURI();
}
RestTemplate restTemplate = getRestTemplate(request);
ResponseEntity<byte[]> forObject = restTemplate.postForEntity(url, body, byte[].class);
setResponseHeader(response, forObject);
//String forObject = HttpUtil.post(url, body, 30000);
System.out.println("result:" + forObject);
return forObject.getBody();
}
/**
* 代理PUT代理
*
* @author 子安
* @date 2022-05-01
*/
// @PutMapping("/**")
// public Object proxyPut(HttpServletRequest request, String body) {
// if (StringUtils.isEmpty(proxy)) {
// return "url为空!";
// }
// String requestURI = request.getRequestURI();
// requestURI = requestURI.replaceAll("/?sdzfw/corpwx/proxy/*", "/");
// //String url = (proxy + requestURI).replaceAll("/+", "/");
// String url = proxy + requestURI;
// String queryString = request.getQueryString();
// if(StringUtils.isNotBlank(queryString)){
// url +="?" + queryString;
// }
// System.out.println("url:" + url);
// restTemplate.put(url, body, Map.class);
// return "ok";
// }
/**
* 设置代理
*
* @param url 代理的地址,如: http://127.0.0.1:8888/test/
* @param content 代理的应用路径,如: tomcat
* @author 子安
* @date 2022-05-01
*/
@GetMapping("/setProxy")
public Object setProxy(HttpServletRequest request, String url, String content, Boolean remove) {
if (StringUtils.isEmpty(url)) {
return "url不能为空!";
}
if (StringUtils.isEmpty(content)) {
return content + "代理不能为空!";
}
if (url.endsWith("/")) {
url.replaceAll("/+$", "");
}
ProxyMapping proxyMapping = new ProxyMapping(content, url, remove);
proxyContainer.add(proxyMapping);
return proxyMapping;
}
/**
* 查询代理
*
* @author 子安
* @date 2022-05-01
*/
@GetMapping("/getProxy")
public Object getProxy(HttpServletRequest request, String content) {
if (StringUtils.isEmpty(content)) {
return proxyContainer;
}
for (ProxyMapping mapping : proxyContainer) {
if (Objects.equals(content, mapping.getContent())) {
return mapping;
}
}
return content + "代理不存在!";
}
/**
* 删除代理
*
* @author 子安
* @date 2022-05-01
*/
@GetMapping("/delProxy")
public Object delProxy(HttpServletRequest request, String content) {
if (StringUtils.isEmpty(content)) {
return "content不能为空!";
}
for (ProxyMapping mapping : proxyContainer) {
if (Objects.equals(content, mapping.getContent())) {
proxyContainer.remove(mapping);
return mapping;
}
}
return content + "代理不存在!";
}
/**
* 获取restTemplate
*
* @param request
* @return
*/
private RestTemplate getRestTemplate(HttpServletRequest request) {
RestTemplate restTemplate = new RestTemplate();
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM, MediaType.TEXT_PLAIN, MediaType.TEXT_HTML));
restTemplate.getMessageConverters().add(mappingJackson2HttpMessageConverter);
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
interceptors.add(new HeaderClientHttpRequestInterceptor(request));
return restTemplate;
}
/**
* 将restTemplate的响应头header设置到客户端response
*
* @param response
* @param forObject
*/
private void setResponseHeader(HttpServletResponse response, ResponseEntity<byte[]> forObject) {
for (Map.Entry<String, List<String>> entry : forObject.getHeaders().entrySet()) {
List<String> value = entry.getValue();
for (String s : value) {
response.setHeader(entry.getKey(), s);
}
}
}
/**
* 解析获取代理后的URL
*
* @param request
* @param map
* @return
* @throws UnsupportedEncodingException
*/
private String getProxyUrl(HttpServletRequest request, Map<String, Object> map) throws UnsupportedEncodingException {
String requestURI = request.getRequestURI();
for (ProxyMapping mapping : proxyContainer) {
String content = mapping.getContent();
String proxyUrl = mapping.getUrl();
Boolean remove = mapping.getRemove();
if (remove) {
if ((requestURI + "/").startsWith("/" + content + "/")) {
proxyUrl += requestURI.replaceAll("^/" + content + "/", "/").replaceAll("/+", "/");
return parseProxyUrlParameter(request, map, proxyUrl);
}
} else if (StringUtils.isEmpty(content) || (requestURI + "/").startsWith("/" + content + "/")) {
proxyUrl += requestURI.replaceAll("/+", "/");
return parseProxyUrlParameter(request, map, proxyUrl);
}
}
return null;
}
private String parseProxyUrlParameter(HttpServletRequest request, Map<String, Object> map, String proxyUrl) throws UnsupportedEncodingException {
StringBuilder sb = new StringBuilder();
if (!CollectionUtils.isEmpty(map)) {
for (String k : map.keySet()) {
sb.append("&").append(k).append("=")
//.append("{").append(k).append("}");
//.append(request.getParameter(k));
//.append(request.getParameter(k).replaceAll("\\+", "%2B"));
//.append(URLEncoder.encode(request.getParameter(k).replaceAll("\\+", "%2B"), "utf-8"));
.append(URLEncoder.encode(request.getParameter(k), "utf-8"));
//Object o = map.get(k);
//if(o instanceof String) {
// map.put(k, ((String) o).replaceAll("\\+", "%2B"));
//}
}
sb.deleteCharAt(0);
proxyUrl += "?" + sb;
}
return proxyUrl;
}
/**
* 请求头拦截类
* 从客户端request获取请求头header然后设置到restTemplate
*
* @author 子安
*/
public static class HeaderClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
private HttpServletRequest httpServletRequest;
public HeaderClientHttpRequestInterceptor(HttpServletRequest httpServletRequest) {
this.httpServletRequest = httpServletRequest;
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
if (httpServletRequest != null) {
Enumeration<String> headerNames = httpServletRequest.getHeaderNames();
while (headerNames.hasMoreElements()) {
List<String> values = new ArrayList<>();
String x = headerNames.nextElement();
System.out.println(x + ":" + httpServletRequest.getHeader(x));
Enumeration<String> headers = httpServletRequest.getHeaders(x);
while (headers.hasMoreElements()) {
String x1 = headers.nextElement();
values.add(x1);
System.out.println(x1);
}
}
}
return execution.execute(request, body);
}
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
2021-06-23 Linux(kali)使用libpcap抓包并在不影响原有通信基础上转发数据包