手写HTTP代理服务器,使用灵活

手写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);
        }
    }
}
posted @   明月心~  阅读(822)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
历史上的今天:
2021-06-23 Linux(kali)使用libpcap抓包并在不影响原有通信基础上转发数据包
点击右上角即可分享
微信分享提示