15 мая 2023 года "Исходники.РУ" отмечают своё 23-летие!
Поздравляем всех причастных и неравнодушных с этим событием!
И огромное спасибо всем, кто был и остаётся с нами все эти годы!

Главная Форум Журнал Wiki DRKB Discuz!ML Помощь проекту

Данный пример был взят из книги "UNIX сервер", написанной Сергеем Дунаевым, а тот в свою очередь этот пример взял с сайта www.javasoft.com
На мой взгляд приведённый ниже исходный код вебсервера ценен как для профессионалов, так и для начинающих. Но в книге Сергея Дунаева описание исходного кода дано вкратце.

Пример очень простого многопоточного НТТР-сервера:

  Листинг 1
import java.io.*;
import java.net.*;
import java.util.*;

class WebServer implements HttpConstants {

  protected static void p(String s) {
	System.out.println(s);
  }

  protected static Properties props = new Properties();
  static Vector threads = new Vector();
  static File root;
  static int port = 80;
  static int timeout = 0;
  static int workers = 10;
  public static ServerSocket ss;

  static void loadProps() throws IOException {
	File f = new File(System.getProperty("user.dir")+File.separator+
								"WebServer.ini");
	if(f.exists()) {
		InputStream is=new BufferedInputStream(new FileInputStream(f));
		props.load(is);
		is.close();
		String r = props.getProperty("root");
		if(r != null) {
		    root = new File(r);
		    if(!root.exists()) {
			throw new Error(root + " doesn't exist as server root");
		    }
		}
		r = props.getProperty("port");
		if(r != null) {
		    port = Integer.parseInt(r);
		}
		r = props.getProperty("timeout");
		if(r != null) {
		    timeout = Integer.parseInt(r);
		}
		r = props.getProperty("workers");
		if(r != null) {
		    workers = Integer.parseInt(r);
		}
	}
  }

  static void printProps() {
	p("root="+root);
	p("port="+port);
	p("timeout="+timeout);
	p("workers="+workers);
  }

  public static void main(String[] a) throws Exception {
	loadProps();
	printProps();
	for(int i=0;i<workers;i++) {
	   Worker w=new Worker(); 
		(new Thread(w, "worker #"+i)).start();
		threads.addElement(w);
	} 
	ss = new ServerSocket(port); 
	while (true) { 
		Socket s = ss.accept(); 
		Worker w=null; 
		synchronized (threads) { 
			if(threads.isEmpty()) { 
				Worker ws = new Worker(); 
				ws.setSocket(s); 
				(new Thread(ws, "additional worker")).start(); 
			} else { 
				w = (Worker) threads.elementAt(0); 
				threads.removeElementAt(0); 
				w.setSocket(s); 
			} 
		} 
	} 
  } 
} 

class Worker extends WebServer implements HttpConstants, Runnable { 
	final static int BUF_SIZE=2048; 
	static final byte[] EOL={(byte)'\r', (byte)'\n' }; 
	/* buffer to use for requests */ 
	byte[] buf; 
	/* Socket to client we're handling */ 
	private Socket s; Worker() { 
		buf=new byte[BUF_SIZE]; 
		s=null; 
	} 

	synchronized void setSocket(Socket s) { 
		this.s=s; 
		notify(); 
	} 

	public synchronized void run() { 
		while(true) { 
			if (s== null) { 
				/* nothing to do */ 
				try{ 
					wait(); 
				} catch (InterruptedException e) { 
					/* should not happen */ 
					continue; 
				} 
			} try { 
				handleClient(); 
			} catch (Exception e) { 
				e.printStackTrace(); 
			} 
			/* go back in wait queue if there's fewer 
			* than numHandler connections. */ 
			s=null; 
			Vector pool = WebServer.threads; 
			synchronized (pool) { 
				if (pool.size()>= WebServer.workers) {
					/* too many threads, exit this one */
					return;
				} else {
		    		pool.addElement(this);
				}
	    	}
		}
   }


  void handleClient() throws IOException {
	InputStream is = new BufferedInputStream(s.getInputStream());
	PrintStream ps = new PrintStream(s.getOutputStream());
	/* we will only block in read for this many milliseconds
	* before we fail with java.io.lnterruptedlOException,
	* alt which point we will abandon the connection.
	*/
	s.setSoTimeout(WebServer.timeout);
	s.setTcpNoDelay(true);
	/* zero out the buffer from last time */
	for(int i = 0; i <BUF_SIZE; i++) {
		buf[i]= 0; 
	} try{ 
		int nread = 0, r = 0; 
outerloop: 
		while (nread < BUF_SIZE) {
			r=is.read(buf, nread, BUF_SIZE nread); 
			if (r== -1) { 
				/* EOF */ 
				return; 
			} 
		int i=nread; 
		nread +=r; 
		for (; i < nread; i++) {
			if (buf[i]== (byte)'\n' || buf[i]== (byte)'\r') { 
				/* read one line */ 
				break outerloop; 
			} 
		} 
	} 
	/* are we doing a GET or just a HEAD */ 
	boolean doingGet; 
	/* beginning of file name */ 
	int index; 
	if (buf[0]== (byte)'G' && 
		buf[1]== (byte)'E' && 
		buf[2]== (byte)'T' && 
		buf[3]== (byte)' ') { 
		doingGet=true; 
		index=4 
	} else if (buf[0]== (byte)'H' && 
		buf[1]== (byte)'E' && 
		buf[2]== (byte)'A' && 
		buf[3]== (byte)'D' && 
		buf[4]== (byte)' ') { 
		doingGet=false; 
		index=5; 
	} else { 
		/* we don't support this method */ 
		ps.print("HTTP/1.0 " + HTTP_BAD_METHOD + 
					" unsupported method type:"); 
		ps.write(buf, 0, 5); 
		ps.write(EOL); 
		ps.flush(); 
		s.close(); 
		return; 
	} 
	int i = 0; 
	/* find the file name, from: 
	* GET /foo/bar.html HTTP/1.0 
	* extract "/foo/bar.html" 
	*/ 
	for (i = index; i < nread; i++) { 
		if (buf[i]== (byte)' ') { 
			break; 
		} 
	} 
	String fname = (new String(buf, 0, index, 
			i-index)).replace('/', File.separatorChar); 
	if (fname.startsWith(File.separator)) { 
		fname = fname.substring(1); 
	} 
	File targ= new File(WebServer.root, fname); 
	if (targ.isDirectory()) { 
		File ind = new File(targ, "index.html"); 
		if (ind.exists()) { 
			targ = ind; 
		} 
	} 
	boolean OK=printHeaders(targ, ps); 
	if (doingGet) { 
		if (OK) { 
			sendFile(targ, ps); 
		} else { 
			send404(targ, ps); 
		} 
	} 
  } finally { 
	s.close(); 
  } 
} 

  boolean printHeaders(File targ, PrintStream ps) throws IOException { 
	boolean ret = false; 
	int rCode=0; 
	if (!targ.exists()) { 
		rCode = HTTP_NOT_FOUND; 
		ps.print("HTTP/1.0 " + HTTP_NOT_FOUND + " not found"); 
		ps.write(EOL); 
		ret = false; 
	} else { 
		rCode = HTTP_OK; 
		ps.print("HTTP/1.0 " + HTTP_OK + " OK"); 
		ps.write(EOL); 
		ret = true; 
	} 
	ps.print("Server: Simple Java"); 
	ps.write(EOL); 
	ps.print("Date: " + (new Date())); 
	ps.write(EOL); 
	if (ret) { 
		if (!targ.isDirectory()) { 
			ps.print("Content-length: "+targ.length()); 
			ps.write(EOL); 
			ps.print("Last Modified: " + (new Date(targ.lastModified()))); 
			ps.write(EOL); 
			String name = targ.getName(); 
			int ind = name.lastIndexOf('.'); 
			String ct = null; 
			if (ind > 0) {
				ct = (String) map.get(name.substring(ind));
	    	}
			if (ct == null) {
				ct = "unknown/unknown";
			}
	    	ps.print("Content-type: " + ct);
	    	ps.write(EOL);
		} else {
	    	ps.print("Content-type: text/html");
	    	ps.write(EOL);
		}
    }
    return ret;
}

  void send404(File targ, PrintStream ps) throws IOException {
	ps.write(EOL);
	ps.write(EOL);
	ps.println("Not Found\n\n"+
		"The requested resource was not found.\n");
    }

    void sendFile(File targ, PrintStream ps) throws IOException {
	InputStream is = null;
	ps.write(EOL);
	if (targ.isDirectory()) {
	    return;
	} else {
	    is = new FileInputStream(targ.getAbsolutePath());
	}

	try{
	    int n;
	    while ((n = is.read(buf)) > 0) {
		ps.write(buf, 0, n);
	    }
	} finally {
		is.close();
	}
    }

    /* mapping of file extensions to content-types */
    static java.util.Hashtable map = new java.util.Hashtable(); static {
	fillMap();
    }

    static void setSuffix(String k, String v) {
	map.put(k, v);
    }

    static void fillMap() {
	setSuffix("", "content/unknown");
	setSuffix(".uu", "application/octet-stream");
	setSuffix(".exe", "application/octet-stream");
	setSuffix(".ps", "application/postscript");
	setSuffix(".zip", "application/zip");
	setSuffix(".sh", "application/x-shar");
	setSuffix(".tar", "application/x-tar");
	setSuffix(".snd", "audio/basic");
	setSuffix(".au", "audio/basic");
	setSuffix(".wav", "audio/x-wav");
	setSuffix(".gif", "image/gif");
	setSuffix(".jpg", "image/jpeg");
	setSuffix(".jpeg", "image/jpeg");
	setSuffix(".htm", "text/html");
	setSuffix(".html", "text/html");
	setSuffix(".text", "text/plain");
	setSuffix(".c", "text/plain");
	setSuffix(".cc", "text/plain");
	setSuffix(".c++", "text/plain");
	setSuffix(".h", "text/plain");
	setSuffix(".pi", "text/plain");
	setSuffix(".txt", "text/plain");
	setSuffix(".Java", "text/plain");
    }
}

interface HttpConstants {
    /** 2XX: generally "OK" */
    public static final int HTTP_OK = 200;
    public static final int HTTP_CREATED = 201;
    public static final int HTTP_ACCEPTED = 202;
    public static final int HTTP_NOT_AUTHORITATIVE = 203;
    public static final int HTTP_NO_CONTENT = 204;
    public static final int HTTP_RESET = 205;
    public static final int HTTP_PARTIAL = 206;

    /** 3XX: relocation/redirect */
    public static final int HTTP_MULT_CHOICE = 300;
    public static final int HTTP_MOVED_PERM = 301;
    public static final int HTTP_MOVED_TEMP = 302;
    public static final int HTTP_SEE_OTHER = 303;
    public static final int HTTP_NOT_MODIFIED = 304;
    public static final int HTTP_USE_PROXY = 305;

    /** 4ХХ: client error */
    public static final int HTTP_BAD_REQUEST = 400;
    public static final int HTTP_UNAUTHORIZED = 401;
    public static final int HTTP_PAYMENT_REQUIRED = 402;
    public static final int HTTP_FORBIDDEN = 403;
    public static final int HTTP_NOT_FOUND = 404;
    public static final int HTTP_BAD_METHOD = 405;
    public static final int HTTP_NOT_ACCEPTABLE = 406;
    public static final int HTTP_PROXY_AUTH = 407;
    public static final int HTTP_CLIENT_TIMEOUT = 408;
    public static final int HTTP_CONFLICT = 409;
    public static final int HTTP_GONE = 410;
    public static final int HTTP_LENGTH_REQUIRED = 411;
    public static final int HTTP_PRECON_FAILED = 412;
    public static final int HTTP_ENTITY_TOO_LARGE = 413;
    public static final int HTTP_REQ_TOO_LONG = 414;
    public static final int HTTP_UNSUPPORTED_TYPE = 415;

    /**5XX: server error*/
    public static final int HTTP_SERVER_ERROR = 500;
    public static final int HTTP_INTERNAL_ERROR = 501;
    public static final int HTTP_BAD_GATEWAY = 502;
    public static final int HTTP_UNAVAILABLE = 503;
    public static final int HTTP_GATEWAY_TIMEOUT = 504;
    public static final int HTTP_VERSION = 505;
}

Работоспособный исходный код можно взять здесь, а так же к серверу прилагается INI-файлик следующего содержания:

  Листинг 2
root=\Wwwroot
port=80
workers=15
timeout=0

root - локальный каталог, из которого Web-сервер берёт файлы;
port - номер порта, на котором сервер прослушивает входящие запросы;
workers - количество рабочих потоков в общем пуле;
timeout - определяет время в миллисекундах для ожидания поступающих байтов из открытого соединения, после которого соединение может быть закрыто. Значение 0 означает, что сервер будет ждать бесконечно долго.

Для того чтобы этот сервер заработал, необходимо откомпилировать его
javac Webserver.java
и запустить на любой машине, где имеется интерпретатор с Java
java WebServer
Могу добавить, что этот сервер был мною откомпилирован и запущен на таких операционных системах как Linux Red Hat 6.2, Win95/98/NT и надеюсь, что может быть использован на любой платформе без изменения кода.

Продолжение следует ....