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文件的项目图: