3.5 使用案例

下面通过一个具体的案例来说明如何使用Java中的I/O相关的功能。这个案例开发的是一个简单的基于文件系统中静态文件的HTTP服务器。在实现中需要用到文件和网络I/O操作相关的API。基本的实现方式是把HTTP请求中的路径映射为文件系统中对应文件的路径,再把文件的内容作为HTTP请求的响应。在对HTTP请求的处理中,需要对一些常见的出错情况进行处理,如文件找不到的情况。HTTP响应中也需要包含正确的HTTP头信息来指明文件的内容类型。

完整的服务器实现如代码清单3-30所示。处理HTTP请求时采用了异步套接字通道,并用一个包含10个线程的线程池来处理请求。对于每个HTTP请求,先读取客户端发送的请求的具体信息,从中提取出请求对应的路径,再把HTTP请求中的路径与服务器所管理的文件的根目录合并在一起,得到完整的文件路径。如果找不到对应的文件,服务器返回404错误;如果找到对应的文件,在HTTP响应中先输出HTTP头信息,再通过Files类的copy方法把文件输入流中包含的数据直接复制到AsynchronousSocketChannel类的对象所对应的输出流中。这一步相当于把HTTP请求头和内容都返回给客户端。如果在读取文件内容时出错,服务器返回500错误。

代码清单3-30 基于文件系统中静态文件的HTTP服务器


public class StaticFileHttpServer{

private static final Logger LOGGER=Logger.getLogger(StaticFileHttpServer.class.getName());

private static final Pattern PATH_EXTRACTOR=Pattern.compile("GET(.*?)HTTP");

private static final String INDEX_PAGE="index.html";

public void start(final Path root)throws IOException{

Asynchronous Channel Groupgroup=Asynchronous Channel Group.with Fixed ThreadPool(10,Executors.default Thread Factory());

final Asynchronous Server Socket Channel server Channel=Asynchronous Server Socket Channel.open(group).bind(new InetSocket Address(10080));

server Channel.accept(null, new Completion Handler<Asynchronous Socket Channel, Void>(){

public void completed(AsynchronousSocketChannel clientChannel, Void attachement){

serverChannel.accept(null, this);

try{

ByteBuffer buffer=ByteBuffer.allocate(1024);

clientChannel.read(buffer).get();

buffer.flip();

String request=new String(buffer.array());

String requestPath=extractPath(request);

Path filePath=getFilePath(root, requestPath);

if(!Files.exists(filePath)){

String error404=generateErrorResponse(404,"Not Found");clientChannel.write(ByteBuffer.wrap(error404.getBytes()));return;

}

LOGGER.log(Level.INFO,"处理请求:{0}",requestPath);

String header=generateFileContentResponseHeader(filePath);

clientChannel.write(ByteBuffer.wrap(header.getBytes())).get();

Files.copy(filePath, Channels.newOutputStream(clientChannel));

}catch(Exception e){

String error=generateErrorResponse(500,"Internal Server Error");

clientChannel.write(ByteBuffer.wrap(error.getBytes()));

LOGGER.log(Level.SEVERE, e.getMessage(),e);

}finally{

try{

clientChannel.close();

}catch(IOException e){

LOGGER.log(Level.WARNING, e.getMessage(),e);

}

}

}

public void failed(Throwable throwable, Void attachement){

LOGGER.log(Level.SEVERE, throwable.getMessage(),throwable);

}

});

LOGGER.log(Level.INFO,"服务器已经启动,文件根目录为:"+root);

}

private String extractPath(String request){

Matcher matcher=PATH_EXTRACTOR.matcher(request);

if(matcher.find()){

return matcher.group(1);

}

return null;

}

private Path getFilePath(Path root, String requestPath){

if(requestPath==null||"/".equals(requestPath)){

requestPath=INDEX_PAGE;

}

if(requestPath.startsWith("/")){

requestPath=requestPath.substring(1);

}

int pos=requestPath.indexOf("?");

if(pos!=-1){

requestPath=requestPath.substring(0,pos);

}

return root.resolve(requestPath);

}

private String getContentType(Path filePath)throws IOException{

return Files.probeContentType(filePath);

}

private String generateFileContentResponseHeader(Path filePath)throws IOException{

StringBuilder builder=new StringBuilder();

builder.append("HTTP/1.1 200 OK\r\n");

builder.append("Content-Type:");

builder.append(getContentType(filePath));

builder.append("\r\n");

builder.append("Content-Length:"+Files.size(filePath)+"\r\n");

builder.append("\r\n");

return builder.toString();

}

private String generateErrorResponse(int statusCode, String message){

StringBuilder builder=new StringBuilder();

builder.append("HTTP/1.1"+statusCode+""+message+"\r\n");

builder.append("Content-Type:text/plain\r\n");

builder.append("Content-Length:"+message.length()+"\r\n");

builder.append("\r\n");

builder.append(message);

return builder.toString();

}

}