java 手动写http静态网页服务器

本文仅仅是实现一个功能非常有限的http服务器。我仅仅实现了请求一个html和请求一个jpg图片。方式只支持GET。不支持http选项。错误代码仅仅会返回200 400 404.支持xml对服务器的配置。本博客内容仅仅完全处于自身娱乐,高手可直接略过。

实现思想

1用java的sax解析服务器配置文件。确定web服务器的root目录,和web服务器运行的端口号。
2启动一个serverSocket等待链接
3获取一个链接之后把所获得的socket传递给新的httpSolver线程。httpSolver负责解析客户端发来的http请求头。
4如果httpSolver根据服务器请求的文件建立一个HttpMessage类。这个类封装了http请求的消息,包括请求的文件,root目录等等。这个httpMessage传递给GetDisk类
5GetDisk负责从硬盘读取。如果java发现请求的文件不在,那么在返回404.如果请求文件成功那么构造消息头返回200在返回锁请求文件。
6httpSolver关闭socket结束一个回话。

工程文件管理方式

java 手动写http静态网页服务器_第1张图片

xml配置文件

server.xml



<server>
    <port>80port>
    <root>d:\Root\root>
server>

server.dtd



<!ELEMENT server (port,root?)>
<!ELEMENT port (#PCDATA)>
<!ELEMENT root (#PCDATA)>

Main.java

Main.java

package httpServer;

public class Main {
    public static void main(String args[])
    {
        ServerClass myServer=new ServerClass();
        myServer.serverStart();
    }
}

ServerClass.java

public class ServerClass {
    private ServerSocket serverSocket;
    private String root;
    private boolean runing;
    private int port;
    public ServerClass()
    {
        runing=false;
        SAXParserFactory saxpf=SAXParserFactory.newInstance();
        SAXParser saxParser;

        try (InputStream in=new FileInputStream("server.xml"))
        {
            saxParser=saxpf.newSAXParser();
            saxParser.parse(in, new Handler());         
        } 
        catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            System.out.println("server.xml no find");
            e.printStackTrace();

        }
        catch (Exception e) {
            // TODO Auto-generated catch block
            System.out.println("parse server.xml error");
            e.printStackTrace();

        } 

    }


    public void serverStart()
    {
        try {
            serverSocket=new ServerSocket(port);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            System.out.println("ServerSocket cann't establish");
            e.printStackTrace();
        }
        runing=true;
        System.out.println("System is runing");
        while(runing)
        {
            try {
                Socket incomingSocket=serverSocket.accept();


                new Thread(new requestHandler(incomingSocket,root)).start();

            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println("System deal one request");
        }
        System.out.println("System is down");
    }

    public void shutDown()
    {
        runing=false;
    }

    private class Handler extends DefaultHandler
    {
            //单独介绍
    }
}

上面的代码就是调用xml解析工具对服务器的一些基本的内容进行设置。因为我们只对配置文件进行读取操作,所以我们可以简单的使用sax作为简单的解析工具.不用使用dom.之后创建一个serversocket等待链接。
在serverclass里面有一个私有的内部嵌套类

private class Handler extends DefaultHandler
    {
        private boolean isPort;
        private boolean isRoot;
        public Handler()
        {
            super();
            isPort=false;
            isRoot=false;

        }


        @Override
        public void characters(char[] ch, int start, int length)
                throws SAXException {

            super.characters(ch, start, length);
            String temp=new String(ch,start,length);

            if(isPort)
            {

                port=Integer.parseInt(temp);
                isPort=false;
            }
            else if(isRoot)
            {

                root=temp;
                isRoot=false;
            }
        }


        @Override
        public void startElement(String uri, String localName, String qName,
                Attributes attributes) throws SAXException {

            super.startElement(uri, localName, qName, attributes);
            if(qName.equals("port"))
            {
                isPort=true;
            }
            else if(qName.equals("root"))
            {
                isRoot=true;
            }
            else if(qName.equals("server"))
            {
                //不操作
            }
            else
            {
                System.out.println("error xml element");
            }

        }

这个类是用于处理sax解析过程的.因为sax在解析的时候是事件回调的方式.我们要重载DefaultHandler里面的函数.sax在解析的过程中,如果碰到element那么久调用startElement函数,并且把元素等等的名字作为参数传递到里面.当一个element结束的时候调用endElement函数.DefaultHandler里面的各个函数都是空操作.所以我们要进行重载来实现我们想要的操作.私有变量isroot等等是因为在进入元素的时候sax统一调用startElement,碰到里面的值得时候比如 < root > D:\root < / root >在碰到D:\root 的时候会调用characters。但是我们怎么知道D:\root是给root的?就是设置一个isroot变量。表示现在的在处理root元素。把里面的值给root。因为其他的元素比如< port > 80 < / port >碰到80的时候同样会调用characters。


 class requestHandler implements Runnable
{
    Socket incomingSocket;
    String root;
    public requestHandler(Socket incomingSocket,String root) {
        this.incomingSocket=incomingSocket;
        this.root=root;
    }
    @Override
    public void run() {
        new httpSolver(incomingSocket,root).serve();
    }


}

这个主要是为了建立一个线程.用于同时处理很多很多请求.

httpSolver.java

public class httpSolver {
    Socket incomingSocket;
    String root;
    public httpSolver(Socket incomingSocket,String root) {
        this.incomingSocket=incomingSocket;
        this.root=root;
    }

    public void serve()
    {

        Scanner in=null;

        OutputStream out=null;
        try {
            in=new Scanner(incomingSocket.getInputStream());
//          out=new PrintWriter(incomingSocket.getOutputStream(),true);
            out=incomingSocket.getOutputStream();
            HttpMessage httpMessage=new HttpMessage();
            httpMessage.root=root;
            while(in.hasNextLine())
            {
                String input=in.nextLine();

                String splitResult[]=null;
                if(input.startsWith("GET")||input.startsWith("get"))
                {
                    splitResult=input.split(new String(" "));

                    if(!splitResult[2].equals("HTTP/1.1"))
                    {
                        new badRequest().work(out);
                        incomingSocket.close();
                        break;
                    }
                    if(splitResult[1].equals("/"))
                    {
                        httpMessage.targetFile="index.html";
                    }
                    else
                    {
                        httpMessage.targetFile=splitResult[1].substring(1,splitResult[1].length());
                    }
                }
                else
                {
                    splitResult=input.split(new String(":"));
                    switch (splitResult[0]) {
                    case "If-Modified-Since":
                        httpMessage.If_Modified_Since=true;
                        httpMessage.If_Modified_SinceString=splitResult[1];
                        break;
                    case "":
                        new GetDisk(httpMessage).work(out);
                        incomingSocket.close();
                        break;
                    default:

                        break;


                    }

                }


            }

        }           
        catch (IOException e) {
            // TODO Auto-generated catch block
            System.out.println("socket io exception");
            e.printStackTrace();
        }
        finally
        {
            try
            {
                if(in!=null)
                {
                    in.close();
                }
                if(out!=null)
                {
                    out.close();
                }

            }
            catch(Exception e)
            {
                e.printStackTrace();
            }
        }

    }
}

这里面包括了一主要的读取循环。不断读取客户端发来的信息。通过解析字符串发现客户端请求的是什么。我们把消息封装到一个HttpMessage里面。这样便于消息的传递,可以把客户端传来的信息都放到HttpMessage里面传递给其他的处理类,比如GetDisk用于读入文件。HttpMessage可以理解为C语言里面的结构体

HttpMessage.java

//HttpMessage.java
public class HttpMessage {
    public String root;
    public String targetFile;
    public  boolean If_Modified_Since=false;
    public String If_Modified_SinceString=null;
}

GetDisk.java

这个类主要处理从磁盘读取文件。并且把应该返回的http头和http内容都拼装起来。其实这里可以使用 原型设计模式 但是我这里只有几个返回形式。所以直接写在了代码里。但这样对代码维护会造成困难。为了便于加快文件的读取速度。读入输出都是使用二进制方式进行。

public class GetDisk
{
    private HttpMessage httpMessage;
    private StringBuilder result;
    private Path path;
    public GetDisk(HttpMessage httpMessage)
    {
        result=new StringBuilder();
        this.httpMessage=httpMessage;

        path=Paths.get(this.httpMessage.root,this.httpMessage.targetFile);

    }

    public void work(OutputStream out)
    {

        if(!Files.exists(path))
        {
            result.append("HTTP/1.1 404 Not Found\r\nConnection: close\r\n");
            result.append("\r\n");

            try {
                out.write(result.toString().getBytes());
                out.flush();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return;

        }


        byte[] fileContents=null;
        try {
            fileContents=Files.readAllBytes(path);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
         if(fileContents!=null)
        {
            result.append("HTTP/1.1 200 OK\r\n");
            if(httpMessage.targetFile.endsWith("html"))
            {
                result.append( "Content-Type: text/html; charset=utf-8\r\n");
            }
            else if(httpMessage.targetFile.endsWith("jpg"))
            {
                result.append( "Content-Type: image/jpeg\r\n");
            }
            result.append( "Keep-Alive: -1\r\n");


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

            try {
                out.write(result.toString().getBytes());
                out.write(fileContents);
                out.flush();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }   


        return;

    }
}

badRequest.java

主要返回无法解析的指令错误。其实我们可以把每个返回代码的处理过程都实现为一个类。然后这些类都共同的继承一个超类。在超类中实现写好http返回头。然后参数有个个子类给出就是使用模板设计模式。方便代码的管理。

public class badRequest {
    StringBuilder result;
    public badRequest()
    {
        result=new StringBuilder();
    }
    public void work(OutputStream out)
    {

            result.append("HTTP/1.1 400 bad request\r\nConnection: close\r\n");
            result.append("\r\n");

            try {
                out.write(result.toString().getBytes());
                out.flush();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return;


    }
}

最终效果
java 手动写http静态网页服务器_第2张图片

想要下载全部文件我已经放到了gitoschina 上面地址
项目地址

你可能感兴趣的:(比较大型项目构建和实现,编程语言)