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();
}
}