目的:
    Java平台下的内部组件之间的通信。
    1.WebService 由于感觉本身Java平台下的Web Service标准就不够统一,相互之间的调用就会有一些问题,更不用说与.net等其他平台了。而且WebService也是对HTTP请求的一次封装,效率上肯定会有损失,所以就不考虑用WebService了。
    2.Socket,包括Java原生的Socket API和nio,本身都很好,效率也会不错,它们之间的区别大概就是资源占用上了。但是使用Socket的通信,有几个比较复杂的地方是:1)协议解析,要订协议,解析及序列化2)粘包分包的处理(这个在长连接的情况下才会出现,可以不在考虑范围内)3)资源的管理,弄不好的话会导致CPU占用较高或者内存不知不觉泄露。
    3.HTTP通信。由于应用是独立的,不能依托于Web容器。Java原生的HttpServer API好像不推荐使用(藏在好深的一个包里com.sun.net.httpserver.*)。
    4.话说Mina的效率很高,是基于nio的异步通信,封装简化了好多。通过比较简单的包装就可以组成一个HTTP Server(下面例子中就是按照Mina官方提供的demo,自己改动了几点形成的)。然后HTTP的Client端也随便封装下就是了。

步骤
1.封装HTTP请求消息类和响应消息类

package com.ajita.httpserver;  

import java.util.Map;
import java.util.Map.Entry; /**
* 使用Mina解析出的HTTP请求对象
*
* @author Ajita
*
*/
public class HttpRequestMessage {
/**
* HTTP请求的主要属性及内容
*/
private Map<String, String[]> headers = null; public Map<String, String[]> getHeaders() {
return headers;
} public void setHeaders(Map<String, String[]> headers) {
this.headers = headers;
} /**
* 获取HTTP请求的Context信息
*/
public String getContext() {
String[] context = headers.get("Context");
return context == null ? "" : context[0];
} /**
* 根据属性名称获得属性值数组第一个值,用于在url中传递的参数
*/
public String getParameter(String name) {
String[] param = headers.get("@".concat(name));
return param == null ? "" : param[0];
} /**
* 根据属性名称获得属性值,用于在url中传递的参数
*/
public String[] getParameters(String name) {
String[] param = headers.get("@".concat(name));
return param == null ? new String[] {} : param;
} /**
* 根据属性名称获得属性值,用于请求的特征参数
*/
public String[] getHeader(String name) {
return headers.get(name);
} @Override
public String toString() {
StringBuilder str = new StringBuilder(); for (Entry<String, String[]> e : headers.entrySet()) {
str.append(e.getKey() + " : " + arrayToString(e.getValue(), ',')
+ "\n");
}
return str.toString();
} /**
* 静态方法,用来把一个字符串数组拼接成一个字符串
*
* @param s要拼接的字符串数组
* @param sep数据元素之间的烦恼歌负
* @return 拼接成的字符串
*/
public static String arrayToString(String[] s, char sep) {
if (s == null || s.length == 0) {
return "";
}
StringBuffer buf = new StringBuffer();
if (s != null) {
for (int i = 0; i < s.length; i++) {
if (i > 0) {
buf.append(sep);
}
buf.append(s[i]);
}
}
return buf.toString();
} } package com.ajita.httpserver; import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map; import org.apache.mina.core.buffer.IoBuffer; public class HttpResponseMessage {
/** HTTP response codes */
public static final int HTTP_STATUS_SUCCESS = 200; public static final int HTTP_STATUS_NOT_FOUND = 404; /** Map<String, String> */
private final Map<String, String> headers = new HashMap<String, String>(); /** Storage for body of HTTP response. */
private final ByteArrayOutputStream body = new ByteArrayOutputStream(1024); private int responseCode = HTTP_STATUS_SUCCESS; public HttpResponseMessage() {
// headers.put("Server", "HttpServer (" + Server.VERSION_STRING + ')');
headers.put("Server", "HttpServer (" + "Mina 2.0" + ')');
headers.put("Cache-Control", "private");
headers.put("Content-Type", "text/html; charset=iso-8859-1");
headers.put("Connection", "keep-alive");
headers.put("Keep-Alive", "200");
headers.put("Date", new SimpleDateFormat(
"EEE, dd MMM yyyy HH:mm:ss zzz").format(new Date()));
headers.put("Last-Modified", new SimpleDateFormat(
"EEE, dd MMM yyyy HH:mm:ss zzz").format(new Date()));
} public Map<String, String> getHeaders() {
return headers;
} public void setContentType(String contentType) {
headers.put("Content-Type", contentType);
} public void setResponseCode(int responseCode) {
this.responseCode = responseCode;
} public int getResponseCode() {
return this.responseCode;
} public void appendBody(byte[] b) {
try {
body.write(b);
} catch (IOException ex) {
ex.printStackTrace();
}
} public void appendBody(String s) {
try {
body.write(s.getBytes());
} catch (IOException ex) {
ex.printStackTrace();
}
} public IoBuffer getBody() {
return IoBuffer.wrap(body.toByteArray());
} public int getBodyLength() {
return body.size();
} }

2.封装Mina的解析HTTP请求和发送HTTP响应的编码类和解码类

package com.ajita.httpserver;  

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.HashMap;
import java.util.Map; import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.apache.mina.filter.codec.demux.MessageDecoderAdapter;
import org.apache.mina.filter.codec.demux.MessageDecoderResult; public class HttpRequestDecoder extends MessageDecoderAdapter {
private static final byte[] CONTENT_LENGTH = new String("Content-Length:")
.getBytes();
static String defaultEncoding;
private CharsetDecoder decoder; public CharsetDecoder getDecoder() {
return decoder;
} public void setEncoder(CharsetDecoder decoder) {
this.decoder = decoder;
} private HttpRequestMessage request = null; public HttpRequestDecoder() {
decoder = Charset.forName(defaultEncoding).newDecoder();
} public MessageDecoderResult decodable(IoSession session, IoBuffer in) {
try {
return messageComplete(in) ? MessageDecoderResult.OK
: MessageDecoderResult.NEED_DATA;
} catch (Exception ex) {
ex.printStackTrace();
} return MessageDecoderResult.NOT_OK;
} public MessageDecoderResult decode(IoSession session, IoBuffer in,
ProtocolDecoderOutput out) throws Exception {
HttpRequestMessage m = decodeBody(in); // Return NEED_DATA if the body is not fully read.
if (m == null) {
return MessageDecoderResult.NEED_DATA;
} out.write(m); return MessageDecoderResult.OK; } /*
* 判断HTTP请求是否完整,若格式有错误直接抛出异常
*/
private boolean messageComplete(IoBuffer in) {
int last = in.remaining() - 1;
if (in.remaining() < 4) {
return false;
} // to speed up things we check if the Http request is a GET or POST
if (in.get(0) == (byte) 'G' && in.get(1) == (byte) 'E'
&& in.get(2) == (byte) 'T') {
// Http GET request therefore the last 4 bytes should be 0x0D 0x0A
// 0x0D 0x0A
return in.get(last) == (byte) 0x0A
&& in.get(last - 1) == (byte) 0x0D
&& in.get(last - 2) == (byte) 0x0A
&& in.get(last - 3) == (byte) 0x0D;
} else if (in.get(0) == (byte) 'P' && in.get(1) == (byte) 'O'
&& in.get(2) == (byte) 'S' && in.get(3) == (byte) 'T') {
// Http POST request
// first the position of the 0x0D 0x0A 0x0D 0x0A bytes
int eoh = -1;
for (int i = last; i > 2; i--) {
if (in.get(i) == (byte) 0x0A && in.get(i - 1) == (byte) 0x0D
&& in.get(i - 2) == (byte) 0x0A
&& in.get(i - 3) == (byte) 0x0D) {
eoh = i + 1;
break;
}
}
if (eoh == -1) {
return false;
}
for (int i = 0; i < last; i++) {
boolean found = false;
for (int j = 0; j < CONTENT_LENGTH.length; j++) {
if (in.get(i + j) != CONTENT_LENGTH[j]) {
found = false;
break;
}
found = true;
}
if (found) {
// retrieve value from this position till next 0x0D 0x0A
StringBuilder contentLength = new StringBuilder();
for (int j = i + CONTENT_LENGTH.length; j < last; j++) {
if (in.get(j) == 0x0D) {
break;
}
contentLength.append(new String(
new byte[] { in.get(j) }));
}
// if content-length worth of data has been received then
// the message is complete
return Integer.parseInt(contentLength.toString().trim())
+ eoh == in.remaining();
}
}
} // the message is not complete and we need more data
return false; } private HttpRequestMessage decodeBody(IoBuffer in) {
request = new HttpRequestMessage();
try {
request.setHeaders(parseRequest(new StringReader(in
.getString(decoder))));
return request;
} catch (CharacterCodingException ex) {
ex.printStackTrace();
} return null; } private Map<String, String[]> parseRequest(StringReader is) {
Map<String, String[]> map = new HashMap<String, String[]>();
BufferedReader rdr = new BufferedReader(is); try {
// Get request URL.
String line = rdr.readLine();
String[] url = line.split(" ");
if (url.length < 3) {
return map;
} map.put("URI", new String[] { line });
map.put("Method", new String[] { url[0].toUpperCase() });
map.put("Context", new String[] { url[1].substring(1) });
map.put("Protocol", new String[] { url[2] });
// Read header
while ((line = rdr.readLine()) != null && line.length() > 0) {
String[] tokens = line.split(": ");
map.put(tokens[0], new String[] { tokens[1] });
} // If method 'POST' then read Content-Length worth of data
if (url[0].equalsIgnoreCase("POST")) {
int len = Integer.parseInt(map.get("Content-Length")[0]);
char[] buf = new char[len];
if (rdr.read(buf) == len) {
line = String.copyValueOf(buf);
}
} else if (url[0].equalsIgnoreCase("GET")) {
int idx = url[1].indexOf('?');
if (idx != -1) {
map.put("Context",
new String[] { url[1].substring(1, idx) });
line = url[1].substring(idx + 1);
} else {
line = null;
}
}
if (line != null) {
String[] match = line.split("\\&");
for (String element : match) {
String[] params = new String[1];
String[] tokens = element.split("=");
switch (tokens.length) {
case 0:
map.put("@".concat(element), new String[] {});
break;
case 1:
map.put("@".concat(tokens[0]), new String[] {});
break;
default:
String name = "@".concat(tokens[0]);
if (map.containsKey(name)) {
params = map.get(name);
String[] tmp = new String[params.length + 1];
for (int j = 0; j < params.length; j++) {
tmp[j] = params[j];
}
params = null;
params = tmp;
}
params[params.length - 1] = tokens[1].trim();
map.put(name, params);
}
}
}
} catch (IOException ex) {
ex.printStackTrace();
} return map;
} }
package com.ajita.httpserver; import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map; import org.apache.mina.core.buffer.IoBuffer; public class HttpResponseMessage {
/** HTTP response codes */
public static final int HTTP_STATUS_SUCCESS = 200; public static final int HTTP_STATUS_NOT_FOUND = 404; /** Map<String, String> */
private final Map<String, String> headers = new HashMap<String, String>(); /** Storage for body of HTTP response. */
private final ByteArrayOutputStream body = new ByteArrayOutputStream(1024); private int responseCode = HTTP_STATUS_SUCCESS; public HttpResponseMessage() {
// headers.put("Server", "HttpServer (" + Server.VERSION_STRING + ')');
headers.put("Server", "HttpServer (" + "Mina 2.0" + ')');
headers.put("Cache-Control", "private");
headers.put("Content-Type", "text/html; charset=iso-8859-1");
headers.put("Connection", "keep-alive");
headers.put("Keep-Alive", "200");
headers.put("Date", new SimpleDateFormat(
"EEE, dd MMM yyyy HH:mm:ss zzz").format(new Date()));
headers.put("Last-Modified", new SimpleDateFormat(
"EEE, dd MMM yyyy HH:mm:ss zzz").format(new Date()));
} public Map<String, String> getHeaders() {
return headers;
} public void setContentType(String contentType) {
headers.put("Content-Type", contentType);
} public void setResponseCode(int responseCode) {
this.responseCode = responseCode;
} public int getResponseCode() {
return this.responseCode;
} public void appendBody(byte[] b) {
try {
body.write(b);
} catch (IOException ex) {
ex.printStackTrace();
}
} public void appendBody(String s) {
try {
body.write(s.getBytes());
} catch (IOException ex) {
ex.printStackTrace();
}
} public IoBuffer getBody() {
return IoBuffer.wrap(body.toByteArray());
} public int getBodyLength() {
return body.size();
} }

3.封装HTTP的Server类及HTTP的Handler处理接口,其中HttpHandler接口是要暴露给外部就行自定义处理的。

package com.ajita.httpserver;  

import java.io.IOException;
import java.net.InetSocketAddress; import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor; public class HttpServer {
/** Default HTTP port */
private static final int DEFAULT_PORT = 8080;
private NioSocketAcceptor acceptor;
private boolean isRunning; private String encoding;
private HttpHandler httpHandler; public String getEncoding() {
return encoding;
} public void setEncoding(String encoding) {
this.encoding = encoding;
HttpRequestDecoder.defaultEncoding = encoding;
HttpResponseEncoder.defaultEncoding = encoding;
} public HttpHandler getHttpHandler() {
return httpHandler;
} public void setHttpHandler(HttpHandler httpHandler) {
this.httpHandler = httpHandler;
} /**
* 启动HTTP服务端箭筒HTTP请求
*
* @param port要监听的端口号
* @throws IOException
*/
public void run(int port) throws IOException {
synchronized (this) {
if (isRunning) {
System.out.println("Server is already running.");
return;
}
acceptor = new NioSocketAcceptor();
acceptor.getFilterChain().addLast(
"protocolFilter",
new ProtocolCodecFilter(
new HttpServerProtocolCodecFactory()));
// acceptor.getFilterChain().addLast("logger", new LoggingFilter());
ServerHandler handler = new ServerHandler();
handler.setHandler(httpHandler);
acceptor.setHandler(handler);
acceptor.bind(new InetSocketAddress(port));
isRunning = true;
System.out.println("Server now listening on port " + port);
}
} /**
* 使用默认端口8080
*
* @throws IOException
*/
public void run() throws IOException {
run(DEFAULT_PORT);
} /**
* 停止监听HTTP服务
*/
public void stop() {
synchronized (this) {
if (!isRunning) {
System.out.println("Server is already stoped.");
return;
}
isRunning = false;
try {
acceptor.unbind();
acceptor.dispose();
System.out.println("Server is stoped.");
} catch (Exception e) {
e.printStackTrace();
}
}
} public static void main(String[] args) {
int port = DEFAULT_PORT; for (int i = 0; i < args.length; i++) {
if (args[i].equals("-port")) {
port = Integer.parseInt(args[i + 1]);
}
} try {
// Create an acceptor
NioSocketAcceptor acceptor = new NioSocketAcceptor(); // Create a service configuration
acceptor.getFilterChain().addLast(
"protocolFilter",
new ProtocolCodecFilter(
new HttpServerProtocolCodecFactory()));
acceptor.getFilterChain().addLast("logger", new LoggingFilter());
acceptor.setHandler(new ServerHandler());
acceptor.bind(new InetSocketAddress(port)); System.out.println("Server now listening on port " + port);
} catch (Exception ex) {
ex.printStackTrace();
}
}
} package com.ajita.httpserver; import org.apache.mina.filter.codec.demux.DemuxingProtocolCodecFactory; public class HttpServerProtocolCodecFactory extends
DemuxingProtocolCodecFactory {
public HttpServerProtocolCodecFactory() {
super.addMessageDecoder(HttpRequestDecoder.class);
super.addMessageEncoder(HttpResponseMessage.class,
HttpResponseEncoder.class);
} } package com.ajita.httpserver; import org.apache.mina.core.future.IoFutureListener;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession; public class ServerHandler extends IoHandlerAdapter {
private HttpHandler handler; public HttpHandler getHandler() {
return handler;
} public void setHandler(HttpHandler handler) {
this.handler = handler;
} @Override
public void sessionOpened(IoSession session) {
// set idle time to 60 seconds
session.getConfig().setIdleTime(IdleStatus.BOTH_IDLE, 60);
} @Override
public void messageReceived(IoSession session, Object message) {
// Check that we can service the request context
HttpRequestMessage request = (HttpRequestMessage) message;
HttpResponseMessage response = handler.handle(request);
// HttpResponseMessage response = new HttpResponseMessage();
// response.setContentType("text/plain");
// response.setResponseCode(HttpResponseMessage.HTTP_STATUS_SUCCESS);
// response.appendBody("CONNECTED"); // msg.setResponseCode(HttpResponseMessage.HTTP_STATUS_SUCCESS);
// byte[] b = new byte[ta.buffer.limit()];
// ta.buffer.rewind().get(b);
// msg.appendBody(b);
// System.out.println("####################");
// System.out.println(" GET_TILE RESPONSE SENT - ATTACHMENT GOOD DIAMOND.SI="+d.si+
// ", "+new
// java.text.SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss.SSS").format(new
// java.util.Date()));
// System.out.println("#################### - status="+ta.state+", index="+message.getIndex()); // // Unknown request
// response = new HttpResponseMessage();
// response.setResponseCode(HttpResponseMessage.HTTP_STATUS_NOT_FOUND);
// response.appendBody(String.format(
// "<html><body><h1>UNKNOWN REQUEST %d</h1></body></html>",
// HttpResponseMessage.HTTP_STATUS_NOT_FOUND)); if (response != null) {
session.write(response).addListener(IoFutureListener.CLOSE);
}
} @Override
public void sessionIdle(IoSession session, IdleStatus status) {
session.close(false);
} @Override
public void exceptionCaught(IoSession session, Throwable cause) {
session.close(false);
}
} package com.ajita.httpserver; /**
* HTTP请求的处理接口
*
* @author Ajita
*
*/
public interface HttpHandler {
/**
* 自定义HTTP请求处理需要实现的方法
* @param request 一个HTTP请求对象
* @return HTTP请求处理后的返回结果
*/
HttpResponseMessage handle(HttpRequestMessage request);
}

4.HTTP Client端;

5.测试,建立测试类如下

package com.jita;  

import java.io.IOException;  

import com.ajita.httpserver.HttpHandler;
import com.ajita.httpserver.HttpRequestMessage;
import com.ajita.httpserver.HttpResponseMessage;
import com.ajita.httpserver.HttpServer; public class TestHttpServer {
public static void main(String[] args) throws IOException,
InterruptedException {
HttpServer server = new HttpServer();
server.setEncoding("GB2312");
server.setHttpHandler(new HttpHandler() {
public HttpResponseMessage handle(HttpRequestMessage request) {
String level = request.getParameter("level");
System.out.println(request.getParameter("level"));
System.out.println(request.getContext());
HttpResponseMessage response = new HttpResponseMessage();
response.setContentType("text/plain");
response.setResponseCode(HttpResponseMessage.HTTP_STATUS_SUCCESS);
response.appendBody("CONNECTED\n");
response.appendBody(level);
return response;
}
});
server.run(); //Thread.sleep(10000);
// server.stop();
}
}

启动,在浏览器中输入HTTP请求如:http://192.168.1.111:8080/test.do?level=1

基于Mina的Http Server以及简单的Http请求客户端的更多相关文章

  1. 一个基于MINA框架应用的最简单例子

    直接上代码.关于原理和主要的API以后在说.先能跑通了在说. 主要的包:mina-core-2.0.0.jar[到官网上下载完整项目包里面还有文档和依赖包],jcl-over-slf4j-1.5.11 ...

  2. 基于MINA构建简单高性能的NIO应用

    mina是非常好的C/S架构的java服务器,这里转了一篇关于它的使用感受. 前言MINA是Trustin Lee最新制作的Java通讯框架.通讯框架的主要作用是封装底层IO操作,提供高级的操作API ...

  3. 基于moco的mock server 简单应用 来玩玩吧

    提起mock大家应该就知道是干嘛用的了,再次再介绍一种简单的方式,基于moco的mock server.步骤很简单: 1. 首先,要下载个moco的jar0_1482402640757_moco-ru ...

  4. 基于Libevent的HTTP Server

    简单的Http Server 使用Libevent内置的http相关接口,可以很容易的构建一个Http Server,一个简单的Http Server如下: #include <event2/e ...

  5. C# .net基于Http实现web server(web服务)

    原文:C# .net基于Http实现web server(web服务) 什么是 web server?  百度百科是这么解释的: Web Server中文名称叫网页服务器或web服务器.WEB服务器也 ...

  6. apache ftp server的简单入门(java应用内嵌ftp server)

    Apache Ftp Server:(强调) Apache Ftp Server 是100%纯Java的FTP服务器软件,它采用MINA网络框架开发具有非常好的性能.Apache FtpServer ...

  7. apache ftp server的简单入门(数据库验证)

    apache的简单校验分为两种,一直是前面提到的properties的校验,具体参考:apache ftp server的简单入门(properties验证) 今天来说一种数据库的校验,这种方式在项目 ...

  8. 【迷你微信】基于MINA、Hibernate、Spring、Protobuf的即时聊天系统:0.概述

    欢迎阅读我的开源项目<迷你微信>服务器与<迷你微信>客户端 序言 帖主和队友仿制了一个简单版的微信,其中,队友是用Unity3D做前段,帖主用Java的Mina.Hiberna ...

  9. 【迷你微信】基于MINA、Hibernate、Spring、Protobuf的即时聊天系统:7.项目介绍之架构(1)

    欢迎阅读我的开源项目<迷你微信>服务器与<迷你微信>客户端 前言 <迷你微信>服务器端是使用Java语言,Mina框架编写的,一个良好的架构关系到后期迭代的方便程度 ...

随机推荐

  1. svn 类似.gitignore功能实现

    svn propset -R svn:ignore -F .cvsignore .

  2. swapper_pg_dir的作用

    在内存系统初始化过程中,有如下代码: 1: static void __init pagetable_init(void) 2: { 3: pgd_t *pgd_base = swapper_pg_d ...

  3. 条件sql ibatis

    <!-- 多条件查询 --><select id="MS-CUSTOM-PANDECT-INFO-BY-CONDITIONS" resultMap="R ...

  4. Day 21 python :面向对象 类的相关内置函数 /单例模式 /描述符

    1.isinstance(obj,cls) 检查obj是否是类cls的对象: 备注:用isinstance 的时候,产生实例后,会显示实例既是父类的实例,也是子类的实例 class Mom: gend ...

  5. angularJS 绑定操作

    <button type="submit" ng-disabled="editForm.$invalid || vm.isSaving" class=&q ...

  6. 2018-12-21-微软最具价值专家-MVP-如何获得-Resharper-的免费功能

    title author date CreateTime categories 微软最具价值专家 MVP 如何获得 Resharper 的免费功能 lindexi 2018-12-21 11:29:0 ...

  7. 微信公众号开发上传图文素材带有卡片小程序报错:errcode=45166,errmsg = invalid content hint

    微信公众号开发自从支持允许在群发图文中插入小程序,方便了小程序的运营及推广.最近在三方服务开发中,要支持图文素材插入小程序遇到了一个很是棘手的问题.官方给出的插入小程序的示例支持文字.图片.卡片.如下 ...

  8. Spring框架4大原则和主要功能

    Spring框架4大原则: 使用POJO进行轻量级和最小侵入式开发 POJO(Plain Ordinary Java Object)简单的Java对象,实际就是普通JavaBeans,是为了避免和EJ ...

  9. HashMap 1.7 与 1.8 的 区别,说明 1.8 做了哪些优化,如何优化的

    JDK1.7用的链表散列结构,JDK1.8用的红黑树 在扩充HashMap的时候,JDK1.7的重新计算hash, JDK1.7只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引 ...

  10. WSGI——python-Web框架基础

    1. 简介 WSGI ​ WSGI:web服务器网关接口,这是python中定义的一个网关协议,规定了Web Server如何跟应用程序交互.可以理解为一个web应用的容器,通过它可以启动应用,进而提 ...