SpringGateway容错扩展:负载均衡备机与滑跳-java实现源码

Spring GateWay在目标服务访问失败时,一般输出fallback报错页面给最终页端如果网关内的此服务只有一个,返回报错页面是合理的。但如果是负载均衡模式,fallback方式就不可取,因为一次页面请求的失败就意味着一次商机的丢失。

下面的扩展代码,将展示对Spring GateWay负载均衡的改进,象nginx一样,增加了一个备份主机服务的功能,当网关对目标服务访问失败时,自动使用备机服务返回页面。下面代码实现的另一种方式是滑跳,即自动滑动到下一台负载均衡主机让它来返回页面。

实现原理:route配置中的:lb://webapi-provider-service-name  假定这个就是负载均衡的服务名,有多台主机,那么在网关执行如上图所示的过滤器A时,代码将把它转换成具体的http://host:port。然后去执行过滤器B,进行异步http请求,如果失败而且当前route没有配置fallback,将引发异常。笔者尝试过先备份现场再在异常线程里重启过滤链,但无法返回页端数据。所以只能在异常处理函数中,发起新的同步http请求到备机服务或下一均衡主机,再把拿到的数据返回页端,结果是成功的。

下面是源码:重点是在异常处理的报错页渲染函数中发起新的http请求,拿到数据再返回。把下面的6个java文件加入到你自己的网关二开项目中,就可以实现负载均衡的备机和滑跳了。


import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;

import java.util.Collections;
import java.util.List;


@Configuration
public class ExceptionConfig {
    @Primary
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public ErrorWebExceptionHandler errorWebExceptionHandler(ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                                             ServerCodecConfigurer serverCodecConfigurer) {
        JsonExceptionHandler jsonExceptionHandler = new JsonExceptionHandler();
        jsonExceptionHandler.setViewResolvers(viewResolversProvider.getIfAvailable(Collections::emptyList));
        jsonExceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters());
        jsonExceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders());
        return jsonExceptionHandler;
    }
}

上面这个是异常处理的Bean,必须要route中没有配置fallback时,它才能生效!

下面是异常处理的具体实现代码,它里面的renderErrorResponse是核心函数,其中涉及到的:

Body数据来源(json String);

属性集合数据来源   getAttributes().get("BankUP_SERVICE_HOST_FLAG")

它们是从再后面的自定义过滤器代码中取得的。


import org.springframework.cloud.gateway.sample.GatewayApplication;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.cloud.gateway.sample.function.MySpringUtil;
import org.springframework.cloud.gateway.sample.service.MyGetNextServiceInstanceHost;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.springframework.cloud.gateway.sample.pub.pub_static_func.sendGetRequest;
import static org.springframework.cloud.gateway.sample.pub.pub_static_func.sendPostRequest;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;


@Slf4j
public class JsonExceptionHandler implements ErrorWebExceptionHandler {
    /**
     * MessageReader
     */
    private List<HttpMessageReader<?>> messageReaders = Collections.emptyList();

    /**
     * MessageWriter
     */
    private List<HttpMessageWriter<?>> messageWriters = Collections.emptyList();

    /**
     * ViewResolvers
     */
    private List<ViewResolver> viewResolvers = Collections.emptyList();

    /**
     * 存储处理异常后的信息
     */
    private ThreadLocal<Map<String, Object>> Thread_exceptionHandlerResult = new ThreadLocal<>();
    /**
     * 参考AbstractErrorWebExceptionHandler
     */
    public void setMessageReaders(List<HttpMessageReader<?>> messageReaders) {
        Assert.notNull(messageReaders, "'messageReaders' must not be null");
        this.messageReaders = messageReaders;
    }

    /**
     * 参考AbstractErrorWebExceptionHandler
     */
    public void setViewResolvers(List<ViewResolver> viewResolvers) {
        this.viewResolvers = viewResolvers;
    }

    /**
     * 参考AbstractErrorWebExceptionHandler
     */
    public void setMessageWriters(List<HttpMessageWriter<?>> messageWriters) {
        Assert.notNull(messageWriters, "'messageWriters' must not be null");
        this.messageWriters = messageWriters;
    }



    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        // 按照异常类型进行处理
        HttpStatus httpStatus;
        String body;
        if (ex instanceof NotFoundException) {
            httpStatus = HttpStatus.NOT_FOUND;
            body = "Service Not Found(网关内部的服务未发现):"+ex.getMessage();
        } else if (ex instanceof ResponseStatusException) {
            ResponseStatusException responseStatusException = (ResponseStatusException) ex;
            httpStatus = responseStatusException.getStatus();
            body = responseStatusException.getMessage();
        } else {
            httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
            body = "Internal Access Server Failed(网关内部的服务器访问失败,可能是IP错或端口未打开,也可能是目标服务尚未运行):";
            body+=ex.getMessage();
        }
        //封装响应体,此body可修改为自己的jsonBody
        Map<String, Object> result = new HashMap<>(2, 1);
        result.put("httpStatus", httpStatus);

        String msg = "{\"code\":" + httpStatus + ",\"zw_report_msg\": \"" + body + "\"}";
        result.put("body", msg);
        result.put("exchange",exchange);//把exchange对象也放入线程存储待处理

        //错误记录
        //ServerHttpRequest request = exchange.getRequest();
        //log.error("[全局异常处理]异常请求路径:{},记录异常信息:{}", request.getPath(), ex.getMessage());
        //参考AbstractErrorWebExceptionHandler
        if (exchange.getResponse().isCommitted()) {
            return Mono.error(ex);
        }

        Thread_exceptionHandlerResult.set(result);//保存线程变量

        ServerRequest newRequest = ServerRequest.create(exchange, this.messageReaders);
        return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse).route(newRequest)
                .switchIfEmpty(Mono.error(ex))
                .flatMap((handler) -> handler.handle(newRequest))
                .flatMap((response) -> write(exchange, response));
    }//end func handle


    //重写出错后的对最终页端的响应
    protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {

        Map<String, Object> result = Thread_exceptionHandlerResult.get();
        HttpStatus hs=(HttpStatus)result.get("httpStatus");
        String body_str=result.get("body").toString();
        ServerWebExchange ec=(ServerWebExchange)result.get("exchange");
        Thread_exceptionHandlerResult.remove();//必须回收内存很重要

        if(hs.value()==504 || hs.value()==500)
            //这两种都是无法访问服务器指定PORT的报错,比如终止服务进程,上面这条可以随时按需改扩
        {
            String second_result="";
            if(ec.getAttributes().get("BankUP_SERVICE_HOST_FLAG")!=null)
            {
                String bank_url="";
                String host=ec.getAttributes().get("BankUP_SERVICE_HOST_FLAG").toString();
                if(host.equals("skip"))//选择的是跳过模式,而不是备机模式
                {
                    URI requestUrl = ec.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
                    String scm=requestUrl.getScheme();//"http://之类的字串"
                    MyGetNextServiceInstanceHost gsh= MySpringUtil.getBean(MyGetNextServiceInstanceHost.class);
                    host= gsh.GetNext(requestUrl.getHost()+":"+requestUrl.getPort(),"lb://webapi-provider-zw");
                    if(!host.isEmpty())host=scm+"://"+host;
                }

                if(!host.isEmpty()) {
                    //下面合成后备访问URL串
                    bank_url = host;
                    String path = ec.getRequest().getURI().getPath();
                    if (path != null) bank_url += path;
                    String para = ec.getRequest().getURI().getQuery();
                    if (para != null) bank_url += "?" + para;

                    //Map<String, Object> body_map=new HashMap<>();
                    //重要,要在这里得到“体串”/
                    String body_json_str = "";
                    if (ec.getAttributes().get("Body_JSON_DATA_GETOUT_String") != null) {
                        body_json_str = ec.getAttributes().get("Body_JSON_DATA_GETOUT_String").toString();
                    }

                    //取得访问方法
                    String method_str = null;
                    try {
                        method_str = ec.getRequest().getMethod().name().toUpperCase();
                    } catch (Exception E) {
                        E.printStackTrace();
                    }

                    //取得头部
                    HttpHeaders hhd = ec.getRequest().getHeaders();

                    //发送HTTP请求并取得返回结果
                    if (method_str != null && method_str.equals("POST")) {
                        second_result = sendPostRequest(
                                bank_url,
                                body_json_str,
                                hhd, null);
                    } else second_result = sendGetRequest(bank_url, hhd);


                    if (second_result.isEmpty())//这种二次结果的情形是访问后备服务器出错的情形。
                    {   //在报错信息上加上:下面的报错
                        body_str += "(remark:网关第二次访问备机失败!)";
                    } else {
                        hs = HttpStatus.OK;
                        body_str = second_result;//原来的报错页被新的数据页覆盖
                    }
                }//end if host!=""
            }//end if BankUP_SERVICE_HOST_FLAG")!=null
        }//end if 500 or 504 er

        //最终结果将会在下面的write()函数上最终输出
        return ServerResponse.status( hs )
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .body(BodyInserters.fromObject(body_str));
    }//end func


    /**
     * 参考AbstractErrorWebExceptionHandler
     * 当发生比如500错的时候 或发生 “请求被拒绝”,下面这个函数就会调用到!我一般用结束“供应服务进程”来模拟测试出它。
     */
    private Mono<? extends Void> write(ServerWebExchange exchange,
                                       ServerResponse response) {        
        exchange.getResponse().getHeaders()
                .setContentType(response.headers().getContentType());
        return response.writeTo(exchange, new ResponseContext());
    }//end func

    /**
     * 参考AbstractErrorWebExceptionHandler
     */
    private class ResponseContext implements ServerResponse.Context {
        @Override
        public List<HttpMessageWriter<?>> messageWriters() {
            return JsonExceptionHandler.this.messageWriters;
        }

        @Override
        public List<ViewResolver> viewResolvers() {
            return JsonExceptionHandler.this.viewResolvers;
        }
    }
}

下在是路由route定义代码:


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;

import org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JCircuitBreakerFactory;
import org.springframework.cloud.gateway.sample.function.RequestBodyRewrite;
import org.springframework.cloud.gateway.sample.function.ResponseBodyRewrite;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;


@SpringBootConfiguration
@EnableAutoConfiguration
@Configuration
public class MyRoutesConfig {
//这个是自定义的路由配置,从代码实现的

    @Bean
    public RouteLocator routes(RouteLocatorBuilder builder, ObjectMapper objectMapper) {
       return builder
               .routes() //下面一共建立了三条路由
               .route("path_route_zw_blog", r -> r.path("/testxxx/**").uri("http://www.163.com:80"))
               .route("path_route_zw_chat", r -> r.path("/testzzz/**").uri("http://www.sina.com.cn:80"))
               .route("path_route_zw_post",
                        r -> r.path("/testp/**")     //主要处理这个路径的请求
                                .filters(f -> f
                                        //.addRequestHeader()
                                        //.addRequestParameter()
                                        //重要的是下面两个来回数据重写的功能的确比较强大
                                        //.modifyRequestBody(String.class,String.class,new RequestBodyRewrite(objectMapper))
                                        //下面这个很重要,修改响应体,在这里可以拿到真实的最后状态码,从而可以做健康状态统计报表
                                        //.modifyResponseBody(String.class, String.class, new ResponseBodyRewrite(objectMapper))
                                        .addResponseHeader("zw-add-header","zw-header-of-value")
                                )
                               .uri("lb://webapi-provider-zw"))
                                //"lb"是负载均衡标志,后面跟的是“服务供应者”的服务名ID
                .build();
              }//end function
}//end class

以上三个文件,我都放在config包下面。

下面是两个全局过滤器的代码,目的是设置属性集合标志和取得Body里的json。我把它们放在filter包下面。

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.net.URI;

import lombok.extern.slf4j.Slf4j;
        import org.springframework.cloud.gateway.filter.GatewayFilterChain;
        import org.springframework.cloud.gateway.filter.GlobalFilter;
        import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
        import org.springframework.core.Ordered;
        import org.springframework.stereotype.Component;
        import org.springframework.web.server.ServerWebExchange;
        import reactor.core.publisher.Mono;

        import java.net.URI;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;


@Component
@Slf4j
public class Middle1GlobalFilter implements GlobalFilter, Ordered {

    @Override
    public int getOrder() {
        return (10000+1); //目的是在netty_router_filter之前执行,在负载过滤器之后执行
    }//这个大数为了能最后执行
    //过滤器的执行优先级,最终都是通过Order值进行排序执行,Order值越小越先执行。
    //两个GlobalFilter类型的过滤器Order值相同时,根据文件名字母排序,文件名靠前的优先更高。
    //在结束处调试观看chain参数,就能看到过滤链,看到所有ORDER和自身的执行顺序

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        log.warn(""+Thread.currentThread().getId()+"*************zw: come in Middle1GlobalFilter ");


        URI requestUri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
        if(requestUri!=null) {
            try {
                if(requestUri.toString().contains("lb://webapi-provider-zw"))//这个是负载均衡的服务名称
                {   //对于特定的负载均衡服务供应者,指明500、504报错后,网关重定向服务取有效数据返回,而不是执行fallback,返回无效的提示数据。
                    //1.可选方式一,指定一个后备服务
                    //exchange.getAttributes().put("BankUP_SERVICE_HOST_FLAG","http://192.168.3.4:9764");
                    //2.可选方式二,当负载均衡服务在两个以上时,可以滑跳到下一个服务。
                    exchange.getAttributes().put("BankUP_SERVICE_HOST_FLAG","skip");
                }
                log.warn("" + Thread.currentThread().getId() + "zw***MiddleGlobalFilter***uri= " + requestUri.toString());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }


        Mono<Void> mv =null;
        mv = chain.filter(exchange);
        return mv;
    }//end func


}//end class

package org.springframework.cloud.gateway.sample.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.List;

import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE;


@Component
@Slf4j
public class Middle2GlobalFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.warn(""+Thread.currentThread().getId()+"*************zw: come in Middle2GlobalFilter ");

        if(exchange.getAttributes().get("BankUP_SERVICE_HOST_FLAG")!=null) {
            ServerHttpRequest request = exchange.getRequest();
            HttpHeaders headers = request.getHeaders();

            //读取BODY体内的JSON数据作为一字符串
            MediaType contentType = headers.getContentType();
            long contentLength = headers.getContentLength();
            if (contentLength > 0) {
                if (MediaType.APPLICATION_JSON.equals(contentType) || MediaType.APPLICATION_JSON_UTF8.equals(contentType)) {
                    return readBody(exchange, chain);
                }
            }
        }

        return chain.filter(exchange);
    }//end func filter


    /**
     * default HttpMessageReader
     */
    private static final List<HttpMessageReader<?>> messageReaders =
            HandlerStrategies.withDefaults().messageReaders();
    /**
     * ReadJsonBody
     *
     * @param exchange
     * @param chain
     * @return
     */
    private Mono<Void> readBody(ServerWebExchange exchange, GatewayFilterChain chain) {
        /**
         * join the body
         */

        return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> {
            byte[] bytes = new byte[dataBuffer.readableByteCount()];
            dataBuffer.read(bytes);
            DataBufferUtils.release(dataBuffer);
            Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
                DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
                DataBufferUtils.retain(buffer);
                return Mono.just(buffer);
            });

            /**
             * repackage ServerHttpRequest
             */
            ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
                @Override
                public Flux<DataBuffer> getBody() {
                    return cachedFlux;
                }

            };
            /**
             * mutate exchage with new ServerHttpRequest
             */
            ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build();
            /**
             * read body string with default messageReaders
             */
            return ServerRequest.create(mutatedExchange, messageReaders).bodyToMono(String.class)
                    .doOnNext(objectValue -> {
                        log.warn("[GatewayContext]Read JsonBody:{}", objectValue);
                        exchange.getAttributes().put("Body_JSON_DATA_GETOUT_String", objectValue);
                    }).then(chain.filter(mutatedExchange));
        });
    }
    @Override
    public int getOrder() {
        return (10000+2);
    }
}

最后,是在异常处理函数中用的到同步HTTP请求获取数据的工具函数代码

sendGetRequest和sendPostRequest这两条同步函数。这个代码我是放在pub包下面。

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.http.HttpHeaders;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
import java.util.Map;


public class pub_static_func {


    public static String sendGetRequest(String url_and_para,HttpHeaders hhd) {
        StringBuilder result = new StringBuilder();
        BufferedReader bufferedReader = null;
        try {
            String urlNameString = url_and_para;
            URL realUrl = new URL(urlNameString);
            // 打开和URL之间的连接
            URLConnection connection = realUrl.openConnection();
            // 设置通用的请求属性
            //connection.setRequestProperty("accept", "*/*");
            //connection.setRequestProperty("connection", "Keep-Alive");
           // connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");

            if(hhd!=null&&hhd.size()>0)
            {
                for(String key:hhd.keySet())
                {
                    connection.setRequestProperty(key,  hhd.get(key).toString());
                }
            }
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");


            // 建立实际的连接
            connection.connect();
            // 获取所有响应头字段
            Map<String, List<String>> map = connection.getHeaderFields();


            // 定义 BufferedReader输入流来读取URL的响应


            InputStream inputStream = null;
            //根据responseCode来获取输入流,此处错误响应码的响应体内容也要获取(看服务端的返回结果形式决定)
            inputStream = connection.getInputStream();
            if(inputStream!=null) {
                Reader rd=new InputStreamReader(inputStream);
                if(rd.ready()) {
                    bufferedReader = new BufferedReader(rd);
                }
            }


            String line;
            while ((line = bufferedReader.readLine()) != null) {
                result.append(line);
            }
        } catch (Exception e) {
            //LOGGER.error("HTTP GET error : {}", e.getMessage());
        }
        // 使用finally块来关闭输入流
        finally {
            try {
                if (bufferedReader != null) {
                    bufferedReader.close();
                }
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }


        return result.toString();
    }



    //下面这个函数实测很OK
    //向一个URL发送请求,并取得返回值String
    public static String sendPostRequest(String url, String body_param, HttpHeaders hhd,String charset) {
        //PrintWriter out = null;
        BufferedReader in = null;
        String result = "";
        OutputStreamWriter out = null;

        try {
            URL realUrl = new URL(url);

            // 打开和URL之间的连接
            HttpURLConnection conn =(HttpURLConnection) realUrl.openConnection();      // 设置通用的请求属性

            //请求头的或可能设置


            conn.setUseCaches(false);
            conn.setInstanceFollowRedirects(true);
            if(charset==null||charset.isEmpty())charset="utf-8";
            conn.setRequestMethod("POST");
             if(hhd!=null&&hhd.size()>0)
             {
                 for(String key:hhd.keySet())
                 {
                     conn.setRequestProperty(key,  hhd.get(key).toString());
                 }
             }
            conn.setRequestProperty("accept", "*/*");//设置成这个,不易报错,兼容性最好!,下面这个反而有所不行。
            //conn.setRequestProperty("Accept", "application/json"); // 设置接收数据的格式
            conn.setRequestProperty("Content-Type","application/json;charset="+charset);// 设置发送数据的格式


            // 发送POST请求必须设置如下两行
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.connect();


            //获取URLConnection对象对应的输出流
            out = new OutputStreamWriter(
                    conn.getOutputStream(), charset); // utf-8编码
            out.append(body_param);
            out.flush();

            // 发送请求参数
            // out.print(body_param);
            // flush输出流的缓冲
            // out.flush();

            InputStream inputStream = null;
            //根据responseCode来获取输入流,此处错误响应码的响应体内容也要获取(看服务端的返回结果形式决定)
            if (HttpURLConnection.HTTP_OK == conn.getResponseCode()) {
                inputStream = conn.getInputStream();
            } else {
                inputStream = conn.getErrorStream();
            }


            // 定义BufferedReader输入流来读取URL的响应
            InputStreamReader isr = new InputStreamReader(inputStream, charset);
            in = new BufferedReader(isr);
            String line;
            while ((line = in.readLine()) != null)
            {
                result += line;
            }
            conn.disconnect();//close link
            //result = URLDecoder.decode(result, charset);
            //result = new String(result.getBytes("UTF-8"),"GBK"); //UTF-8转GBK
        }
        catch (Exception e)
        {
            System.out.println("发送 POST 请求出现异常!" + e);
            e.printStackTrace();
        }    // 使用finally块来关闭输出流、输入流

        finally
        {
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (IOException ex) {  ex.printStackTrace();   }
        }

        return result;
    }//end func


}//end class

这些源码都是从CSDN的其它网文或github取下来改改而成的,目前只是组合好调通,实际应用还需要进一步做速度优化和稳定测试。

 下面是这6个JAVA文件的项目图:

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

weixin_42183500

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值