java 自带HttpServer创建接口服务端

本文介绍如何使用Java内置的HttpServer类实现RESTful服务,包括HttpServer、HttpContext、HttpHandler和HttpExchange的基本使用,以及解决Httpserver类被Eclipse禁用的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

实际开发中我们经常遇到接口开发,最近遇到比较老的项目,采用的是java内置的HttpServer类实现的Restful服务。

HttpServer是JDK1.6以后内置的HTTP服务器,位置在rt.jarcom.sun.net.httpserver包下。

支持http和https协议。

主要用到HttpServer类 ,Handler接口实现类,以及HttpExchange类。我们用HttpServer调用Handler接口实现类,通过HttpExchange接受请求信息,并处理返回信息。

1.HttpServer:表示一个服务器实例,需要绑定一个IP地址和端口号。(HttpsServer是其子类,处理https请求)

// 重新绑定地址和端口
void bind​(InetSocketAddress addr, int backlog)
// 获取当前绑定的地址
InetSocketAddress getAddress​()

/**
 * 创建监听的上下文, 请求 URI 根路径的匹配, 根据不同的 URI 根路径选择不同的 HttpHandler 处理请求,
 * 路径必须以 "/" 开头。路径 "/" 表示匹配所有的请求 URI(没有其他更具体的匹配路径除外)。
 */
HttpContext createContext​(String path)
HttpContext createContext​(String path, HttpHandler handler)

// 移除上下文监听
void removeContext​(HttpContext context)
void removeContext​(String path)

// 设置请求的线程执行器, 设置为 null 表示使用默认的执行器
void setExecutor​(Executor executor)
Executor getExecutor​()

// 启动服务
void start​()
// 最长等待指定时间后停止服务
void stop​(int delay)

2.HttpContext:服务器监听器的上下文,需要配置用于匹配URI的公共路径和用来处理请求的HttpHandler
(可以创建多个 HttpContext,一个 HttpContext 对应一个 HttpHandler,不同的 URI 请求,根据添加的 HttpContext 监听器,分配到对应的 HttpHandler 处理请求)
3.HttpHandler:上下文对应的http请求处理器
4.HttpExchange:监听器回调时传入的参数,封装了http请求和响应的所有数据操作

// 获取请求的 URI, 请求链接除去协议和域名端口后的部分, 如: http://www.abc.com/aa/bb, URI 为 /aa/bb
URI getRequestURI​()

// 获取请求客户端的 IP 地址
InetSocketAddress getRemoteAddress​()

// 获取请求协议, 例如: HTTP/1.1
String getProtocol​()

// 获取请求的方法, "GET", "POST" 等
String getRequestMethod​()

// 获取所有的请求头
Headers getRequestHeaders​()

// 以输入流的方式获取请求内容
InputStream getRequestBody​()

// 获取响应头的 Map, 要添加头, 获取到 headers 后调用 add(key, value) 方法添加
Headers getResponseHeaders​()

// 发送响应头, 并指定 响应code 和 响应内容的长度
void sendResponseHeaders​(int rCode, long responseLength)

// 获取响应内容的输出流, 响应内容写到该流
OutputStream getResponseBody​()

// 关闭处理器, 同时将关闭请求和响应的输入输出流(如果还没关闭)
void close​()

// 获取此请求对应的上下文对象
HttpContext getHttpContext​()

// 获取收到请求的本地地址
InetSocketAddress getLocalAddress​()

由于Httpserver类是eclipse默认是进行禁用的状态。所以写程序时会报错。我们可以通过两种方式处理解决这个问题:

1.将jre移除项目再重新导入

右键此处,build path -》remove from build path 然后在导入到项目中,右键

build path-》configure

2. 

项目右键->properties->java builder path->Libraries标签,点击JRE System Library里面的Access rules项中添加 accessible为com/sun/net/httpserver/*** 保存退出。

 实现代码如下:

1.创建HtttpServer 主程序

package com.test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.sun.net.httpserver.HttpServer;

public class ThreadHttpServer {
	//启动端口8080
	private static final int port=8880;
	private static final String Httpcontext="/demo";
	private static final int nThreads=8;
	public static void main(String[] args) {
		HttpServer httpServer;
		try {
			httpServer=HttpServer.create(new InetSocketAddress(port),0);
			httpServer.createContext(Httpcontext,new HttpHandlerDemo() );
//			设置并发数
			ExecutorService  executor=Executors.newFixedThreadPool(nThreads);
			httpServer.setExecutor(executor);
			httpServer.start();
			System.out.println("启动端口:"+port);
			System.out.println("根节点:"+Httpcontext);
			System.out.println("并发数:"+nThreads);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

2.实现HttpHandler接口

package com.test;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.URI;

import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;


public class HttpHandlerDemo   implements HttpHandler{

	@Override
	public void handle(HttpExchange httpExchange) throws IOException {
		//请求地址
		InetSocketAddress inetSocketAddress=httpExchange.getRemoteAddress();
		System.out.println("请求ip地址:"+inetSocketAddress);
		System.out.println("请求host:"+inetSocketAddress.getHostName());
		System.out.println("请求port:"+inetSocketAddress.getPort());
		//请求方式
		String requestMethod=httpExchange.getRequestMethod();
		System.out.println("请求方式:"+httpExchange.getRequestMethod());
		//url
		URI url=httpExchange.getRequestURI();
		System.out.println("url:"+url);
		 if(requestMethod.equalsIgnoreCase("GET")){//客户端的请求是get方法
             //设置服务端响应的编码格式,否则在客户端收到的可能是乱码
             Headers responseHeaders = httpExchange.getResponseHeaders();
             responseHeaders.set("Content-Type", "text/html;charset=utf-8");

             //在这里通过httpExchange获取客户端发送过来的消息
             //URI url = httpExchange.getRequestURI();
             //InputStream requestBody = httpExchange.getRequestBody();

             String response = "this is server";

             httpExchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, response.getBytes("UTF-8").length);

             OutputStream responseBody = httpExchange.getResponseBody();
             OutputStreamWriter writer = new OutputStreamWriter(responseBody, "UTF-8");
             writer.write(response);
             writer.close();
             responseBody.close();  
         } 
		 else {
			//请求报文
				InputStream inputStream=httpExchange.getRequestBody();
				ByteArrayOutputStream  bas=new ByteArrayOutputStream();
				int i;
				while((i=inputStream.read())!=-1) {
					bas.write(i);
				}
				String requestmsg=bas.toString();
				System.out.println("请求报文:"+requestmsg);
				
				//返回报文
				String resmsg="恭喜你成功了!";
				httpExchange.sendResponseHeaders(HttpURLConnection.HTTP_OK,resmsg.getBytes("UTF-8").length );
				OutputStream outputStream=httpExchange.getResponseBody();
				outputStream.write(resmsg.getBytes("UTF-8"));
				outputStream.close();
				System.out.println("通讯结束!");
		}		
	}

}

3.启动主程序,然后调用就可以看见返回结果了。客户端可以自己写,也可以直接访问网页。

客户端代码如下:

package com.test.HttpClient;

import java.io.IOException;
import java.io.UnsupportedEncodingException;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * Hello world!
 *
 */
public class HttpClientApp 
{
	private static final Logger logger = LoggerFactory.getLogger(HttpClientApp.class);
    public static void main( String[] args )
    {
    	String url="http://127.0.0.1:8880/demo";
    	String reqStr="httpserver测试练习";
    	String contentType="application/json"; 
    	String charset="UTF-8";
    	String ss=doPost( url,  reqStr,  contentType,  charset) ;
    	System.out.println("返回内容为:" +ss);
    }
    /**
     * 执行一个HTTP POST请求,返回请求响应的HTML
     *
     * @param url     请求的URL地址
     * @param reqStr  请求的查询参数,可以为null
     * @param charset 字符集
     * @return 返回请求响应的HTML
     */
    public static String doPost(String url, String reqStr, String contentType, String charset) {
    	System.out.println("发送报文:"+reqStr);
        HttpClient client = new HttpClient();
        PostMethod method = new PostMethod(url);
        try {
            HttpConnectionManagerParams managerParams = client.getHttpConnectionManager().getParams();
            managerParams.setConnectionTimeout(30000); // 设置连接超时时间(单位毫秒)
            managerParams.setSoTimeout(30000); // 设置读数据超时时间(单位毫秒)

            method.setRequestEntity(new StringRequestEntity(reqStr, contentType, "utf-8"));

            client.executeMethod(method);
            System.out.println("返回的状态码为:" +method.getStatusCode());
            if (method.getStatusCode() == HttpStatus.SC_OK) {
//                return StreamUtils.copyToString(method.getResponseBodyAsStream(),Charset.forName(charset));
              String resultStr= IOUtils.toString(method.getResponseBodyAsStream(),"utf-8");
              System.out.println("resultStr:"+resultStr);
//              return  new String(resultStr.getBytes("ISO-8859-1"),"UTF-8");
              return resultStr;
                
            }
        } catch (UnsupportedEncodingException e1) {
            logger.error(e1.getMessage());
            return "";
        } catch (IOException e) {
            logger.error("执行HTTP Post请求" + url + "时,发生异常!" + e.toString());
            return "";
        } finally {
            method.releaseConnection();
        }
        return null;
    }
    /**
     * 执行一个HTTP POST请求,返回请求响应的HTML
     *
     * @param url     请求的URL地址
     * @param reqStr  请求的查询参数,可以为null
     * @param charset 字符集
     * @return 返回请求响应的HTML
     */
    public static String doPostMuStr(String url, Part[] reqStr) {
        HttpClient client = new HttpClient();

        PostMethod method = new PostMethod(url);
        try {
            HttpConnectionManagerParams managerParams = client.getHttpConnectionManager().getParams();
            managerParams.setConnectionTimeout(30000); // 设置连接超时时间(单位毫秒)
            managerParams.setSoTimeout(30000); // 设置读数据超时时间(单位毫秒)
//            method.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, "utf-8");
            method.setRequestEntity(new MultipartRequestEntity(reqStr, method.getParams()));
//            method.setContentChunked(true);
            client.executeMethod(method);
            System.out.println("返回的状态码为:" +method.getStatusCode());
            if (method.getStatusCode() == HttpStatus.SC_OK) {
              return IOUtils.toString(method.getResponseBodyAsStream(),"utf-8");              
            }
        } catch (UnsupportedEncodingException e1) {
            logger.error(e1.getMessage());
            return "";
        } catch (IOException e) {
            logger.error("执行HTTP Post请求" + url + "时,发生异常!" + e.toString());
            return "";
        } finally {
            method.releaseConnection();
        }
        return null;
    }    
}

结果如下:

访问网页地址:

项目源代码客户端 服务端已打包 可直接下载

//download.csdn.net/download/xiaolenglala/11967603

<think>好的,我现在需要解决用户的问题:在Spring Boot项目中,使用@ServerEndpoint创建的WebSocket服务端,在Java服务重启时如何优雅关闭。用户希望优雅地关闭服务端,避免强制断开导致客户端出现问题。 首先,我要回忆一下Spring Boot中WebSocket的使用。用户提到使用了@ServerEndpoint注解,这通常意味着他们可能没有使用Spring的WebSocket支持,而是依赖于Java自带的API,比如javax.websocket。在Spring Boot中,如果要使用@ServerEndpoint,通常需要配置一个ServerEndpointExporter的Bean来扫描这些端点。 接下来,用户的问题是关于优雅关闭。优雅关闭一般指在关闭应用前,先处理完当前的请求,释放资源,通知客户端,然后再终止服务。对于WebSocket来说,可能需要关闭所有活跃的连接,并阻止新的连接建立。 首先,我需要考虑如何捕获服务关闭的事件。在Spring Boot中,可以通过@PreDestroy注解或者实现DisposableBean接口来在Bean销毁前执行一些操作。或者,可以监听ContextClosedEvent事件,当应用上下文关闭时触发。 然后,如何获取所有活跃的WebSocket会话。每个使用@ServerEndpoint注解的类在实例化时,可能通过@OnOpen方法将Session保存到一个静态集合中,或者通过其他方式管理。因此,用户需要确保在WebSocket端点类中维护了一个所有活跃Session的集合,比如使用ConcurrentHashMap来保存,并在@OnOpen和@OnClose方法中添加和移除Session。 当服务关闭时,需要遍历所有活跃的Session,依次调用session.close()方法,并可能发送一个关闭消息或状态码,通知客户端连接即将关闭。同时,可能需要设置一个超时时间,避免某些Session无法及时关闭导致服务无法终止。 另外,可能还需要停止接受新的连接请求。在关闭过程中,新的连接请求应该被拒绝,这可能需要通过某种机制,比如设置一个标志位,在@OnOpen方法中检查该标志位,如果服务正在关闭,则拒绝新连接。 接下来,我需要考虑如何将这些步骤整合到Spring Boot的生命周期中。例如,在Bean销毁前,或者在收到ContextClosedEvent时,触发关闭WebSocket连接的逻辑。 可能还需要注意线程安全问题,比如在遍历Session集合时,可能会有并发的添加或移除操作,需要使用线程安全的集合,并在遍历时进行适当的同步处理。 此外,Tomcat或其他Servlet容器在关闭时,可能会自动关闭WebSocket连接,但可能不够优雅,比如直接断开而不发送关闭帧。因此,主动关闭每个Session,发送适当的关闭码和原因,是更优雅的方式。 还需要考虑的是,当应用接收到终止信号(如kill命令)时,如何触发关闭流程。Spring Boot的优雅关闭通常可以通过配置server.shutdown=graceful,并设置超时时间。这可能与WebSocket的关闭流程配合使用,确保在Servlet容器关闭前,WebSocket连接已经被妥善处理。 总结一下,解决方案的大致步骤可能是: 1. 在WebSocket端点类中维护所有活跃的Session。 2. 监听Spring Boot的关闭事件(如ContextClosedEvent)。 3. 在关闭事件触发时,遍历所有Session,发送关闭消息并关闭。 4. 阻止新的连接在关闭过程中被建立。 5. 处理可能出现的异常和超时,确保服务能顺利终止。 需要验证这些步骤是否正确,比如如何正确获取所有Session,如何确保关闭事件被触发,以及如何处理可能的问题,如某些Session无法关闭的情况。同时,要考虑不同Servlet容器的行为差异,比如Tomcat和Jetty可能会有不同的处理方式,但@ServerEndpoint通常是标准API,应该与容器无关。</think>在Spring Boot项目中,使用`@ServerEndpoint`创建的WebSocket服务端,需通过以下步骤实现优雅关闭,确保服务重启时客户端连接被妥善处理: --- ### **1. 管理活跃的WebSocket会话** 在WebSocket端点类中,**维护一个静态集合存储所有活跃的Session**,确保能遍历并关闭它们: ```java @ServerEndpoint("/ws") @Component public class WebSocketEndpoint { private static final Set<Session> sessions = ConcurrentHashMap.newKeySet(); @OnOpen public void onOpen(Session session) { sessions.add(session); } @OnClose public void onClose(Session session) { sessions.remove(session); } // 获取所有活跃会话的方法 public static Set<Session> getActiveSessions() { return sessions; } } ``` --- ### **2. 监听Spring Boot关闭事件** 通过实现`DisposableBean`接口或监听`ContextClosedEvent`事件,触发关闭逻辑: ```java @Component public class WebSocketShutdownHook implements DisposableBean { @Override public void destroy() throws Exception { closeAllWebSocketConnections(); } private void closeAllWebSocketConnections() { Set<Session> sessions = WebSocketEndpoint.getActiveSessions(); for (Session session : sessions) { try { // 发送自定义关闭消息(可选) if (session.isOpen()) { session.getAsyncRemote().sendText("SERVER_SHUTDOWN"); } // 发送标准关闭帧(状态码和原因) session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "Server shutdown")); } catch (IOException e) { // 处理异常 } } } } ``` --- ### **3. 阻止新连接的建立** 在关闭过程中**拒绝新连接**,通过标志位控制: ```java @ServerEndpoint("/ws") @Component public class WebSocketEndpoint { private static volatile boolean isShuttingDown = false; @OnOpen public void onOpen(Session session) throws IOException { if (isShuttingDown) { session.close(new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, "Server is shutting down")); return; } sessions.add(session); } // 设置关闭标志的方法 public static void setShuttingDown(boolean shuttingDown) { isShuttingDown = shuttingDown; } } ``` --- ### **4. 整合Spring Boot优雅关闭** 在`application.properties`中启用优雅关闭(需Spring Boot 2.3+): ```properties server.shutdown=graceful spring.lifecycle.timeout-per-shutdown-phase=30s ``` 这会确保HTTP请求处理完毕后关闭容器,结合WebSocket关闭逻辑更安全。 --- ### **5. 完整关闭流程示例** ```java @Component public class WebSocketShutdownHook implements DisposableBean { @Override public void destroy() { WebSocketEndpoint.setShuttingDown(true); // 阻止新连接 closeAllWebSocketConnections(); // 关闭现有连接 } private void closeAllWebSocketConnections() { // 遍历并关闭所有Session(同上) } } ``` --- ### **注意事项** - **线程安全**:使用`ConcurrentHashMap`或`CopyOnWriteArraySet`管理Session集合。 - **超时处理**:若某些Session未及时关闭,需设置超时(如`session.setMaxIdleTimeout(5000)`)。 - **容器兼容性**:不同Servlet容器(Tomcat/Jetty)对WebSocket关闭的行为一致,但需测试验证。 --- 通过以上步骤,服务重启时会主动通知客户端、关闭连接,避免强制断开导致的问题。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值