SSE和文件下载代理Java版

背景

我们通常是用nginx作为代理服务器,把用户的请求转发到实际的application服务器,作为简单的转发是可以这么做的,但是有时候,我们还需要一些业务处理,那么就需要自己手写代理服务器了,以下介绍使用Java怎么写代理服务器,可以用来代理普通http请求、SSE请求、文件下载请求。

准备

pom文件的依赖如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.9</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example.sse</groupId>
	<artifactId>sseproxy</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>demo</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>11</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-webflux</artifactId>
		</dependency>
		<dependency>
			<groupId>org.apache.httpcomponents</groupId>
			<artifactId>httpclient</artifactId>
			<version>4.5.13</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

代理普通HTTP请求

使用Spring自带的RestTemplate来代理普通的http请求

    @Autowired
    private RestTemplate restTemplate;

    /**
     * forward common http request
     * @param body
     * @param method
     * @param request
     * @return
     * @throws URISyntaxException
     */
    @GetMapping("/sseproxy2")
    public ResponseEntity<String> sseproxy2(@RequestBody(required = false) String body,
                                            HttpMethod method,
                                            HttpServletRequest request) throws URISyntaxException {
        String server = "localhost";
        int port = 8080;
        URI uri = new URI("http", null, server, port, "/sse", request.getQueryString(), null);

        MultiValueMap<String, String> headers = null;
        HttpEntity<String> entity = new HttpEntity<>(body, headers);

        try {
            ResponseEntity<String>  responseEntity =
                    restTemplate.exchange(uri, method, entity, String.class);
            return responseEntity;
        } catch (HttpClientErrorException ex) {
            return ResponseEntity
                    .status(ex.getStatusCode())
                    .headers(ex.getResponseHeaders())
                    .body(ex.getResponseBodyAsString());
        }
    }

代理SSE

使用SseEmitter代理SSE请求

    /**
     * forward request of type SSE
     *
     * @return
     */
    @GetMapping("/sseproxy")
    public SseEmitter sseproxy() {
        SseEmitter sseEmitter = new SseEmitter();

        WebClient webClient = WebClient.create("http://localhost:8080/");
        ParameterizedTypeReference<ServerSentEvent<String>> type = new ParameterizedTypeReference<>() {};
        Flux<ServerSentEvent<String>> eventStream = webClient.get()
                .uri("/sse")
                .retrieve()
                .bodyToFlux(type);

        eventStream.subscribe(ctx -> {
            logger.info("Current time: {}, content [{}]", LocalTime.now(), ctx.data());
            SseEmitter.SseEventBuilder event = SseEmitter.event()
                    .data("SSE MVC - " + LocalTime.now().toString())
                    .name("sse event - mvc");
            try {
                sseEmitter.send(event);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });

        return sseEmitter;
    }

文件下载

package com.example.sse.demo.util;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;

public class ProxyUtil {
    private static final Logger logger = LoggerFactory.getLogger(ProxyUtil.class);

    /**
     * a proxy for download type
     *
     * @param response
     */
    public static void download(HttpServletResponse response,
                                CloseableHttpClient httpClient,
                                String url,
                                Map<String, Object> bodyParams) throws IOException {
        logger.info("start download proxy for url:{}, bodyParams:{}", url, bodyParams.toString());
        response.setContentType("application/octet-stream");
        OutputStream out = null;
        BufferedReader in = null;
        try {
            out = response.getOutputStream();

            HttpPost httpPost = new HttpPost(url);
            httpPost.setHeader("Content-type", "application/json");

            ObjectMapper mapper = new ObjectMapper();
            ObjectNode node = mapper.createObjectNode();
            for (String key : bodyParams.keySet()) {
                node.put(key, String.valueOf(bodyParams.get(key)));
            }
            httpPost.setEntity(new StringEntity(node.toString(), ContentType.APPLICATION_JSON));

            in = new BufferedReader(
                    new InputStreamReader(httpClient.execute(httpPost).getEntity().getContent()));

            logger.info("forward data:");
            String inputLine;
            while ((inputLine = in.readLine()) != null) {
                System.out.println(inputLine);
                out.write(inputLine.getBytes(StandardCharsets.UTF_8));
                out.flush();
            }
        } catch (IOException e) {
            logger.error("proxy download encountered error:" + e.getMessage());
            throw new RuntimeException(e);
        } finally {
            out.close();
            out.flush();
            in.close();
        }
        logger.info("end download proxy");
    }
}

调用端:

    @GetMapping("/downloadproxy")
    public void downloadproxy(HttpServletResponse response) throws IOException {
        Map<String, Object> bodyParams = new HashMap<>();
        bodyParams.put("username", "xiaohei");
        bodyParams.put("age", 3);
        ProxyUtil.download(response,
                HttpClients.createDefault(),
                "http://127.0.0.1:8080/sse3",
                bodyParams
        );
    }

参考

  1. SSE 全称Server Sent Event,直译一下就是服务器发送事件
  2. https://zhuanlan.zhihu.com/p/614824613
  3. https://stackoverflow.com/questions/14726082/spring-mvc-rest-service-redirect-forward-proxy
  4. https://copyprogramming.com/howto/how-to-stream-response-body-with-apache-httpclient#what-is-the-difference-between-httpclient-and-httpasyncclient
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值