澳门新浦京8455comChapter1 一个简单的Web服务器

澳门新浦京8455com 3

自身用过Servlets、JSP、JAX-途乐S、
Spring框架、Play框架、带Facelets的JSF以致SparkFramework。以作者之见,那个框架并不曾很好地促成面向对象设计。它们充满着静态方法、未经测量检验的数据布局以致相当不够美观的消除措施。由此叁个月前本人决定开端编写制定本人的Java
Web框架,作者制订了一部分大旨的信条:1卡塔尔(قطر‎ 未有NULL,2卡塔尔 未有public
static方法,3State of Qatar 未有可变类(mutable class),4卡塔尔国没有类型转换、反射和instanceof操作。那四条基本法规应该丰硕保障干净的代码和透亮的布局。那正是Takes框架诞生的因由。让我们看看那是何等贯彻的。

初始化

装有 Flask 程序都必须创立多少个 app 实例。Web 服务器使用 Web
服务器网关接口左券(Web Server Gateway Interface,
WSGI卡塔尔国把选取自客商端的装有央浼都传送给这几个对象拍卖。 app 实例是 Flask
类的指标:

from flask import Flask
app = Flask(__name__)

Flask 类的布局函数自有二个必得钦点的参数, 即 app 主模块或包的名字。

Flask 用 name 这几个参数决定程序的根目录,
以便稍后能够找到相对于程序根目录的资源文本地点。

Chapter1 八个轻易易行的Web服务器

Java Web架构简介

简言之来讲,那便是本人对叁个Web应用布局以至其组件的明亮。

率先,要创造三个Web服务器,我们应有新创制一个网络套接字(socket),其将会在特定的TCP端口选用连接诉求。平日那几个端口是80,可是为了有扶助测量试验自个儿将利用8080端口。那么些在Java中用ServerSocket类成就。

import java.net.ServerSocket;
public class Foo {
  public static void main(final String... args) throws Exception {
    final ServerSocket server = new ServerSocket(8080);
    while (true);
  }
}

那些丰盛去运营一个Web服务器。现在,socket已经就绪监听8080端口。当有人在浏览器打开  ,将会确立连接况兼等待的齿轮在浏览器上不停的转动。编写翻译这个有些试一下。大家刚刚未有使用其余框架搭建了三个简单易行的Web服务器。大家并不曾对进入的总是做任何工作,然而也不曾拒绝它们。全体的三回九转都正在服务器对象内部排队。那个在后台线程中变成,那正是为何必要在最后放五个while(true卡塔尔国的原故。未有那些非常循环,应用将会马上甘休操作何况服务器套接字将会停业。

下一步是经受走入的连天。在Java中,通过对 accept(卡塔尔 方法的堵截调用来形成。

final Socket socket = server.accept();

那几个方法将会一贯不通线程等待直到叁个新的接连到达。新连接生平出,accept(卡塔尔方法就能够回来一个Socket实例。为了承当下二个接连,咱们将会再度调用
accept(State of Qatar 方法。由此同理可得,大家的Web服务器将会像上边雷同干活:

public class Foo {
  public static void main(final String... args) throws Exception {
    final ServerSocket server = new ServerSocket(8080);
    while (true) {
      final Socket socket = server.accept();
      // 1. Read HTTP request from the socket
      // 2. Prepare an HTTP response
      // 3. Send HTTP response to the socket
      // 4. Close the socket
    }
  }
}

那是个极端循环。不断采取新的连天乞请,识别乞请、创设响应、重返响应,然后再度收到新的总是。HTTP合同是无状态的,那象征服务器不应有深深记住先前其它一个再三再四产生了怎么着。它所关注的是在特定连接中传播的HTTP须求。

HTTP央浼来自于套接字的输入流中,有如多行的文本块。那正是您读取套接字的输入流将会看出的剧情:

final BufferedReader reader = new BufferedReader(
  new InputStreamReader(socket.getInputStream())
);
while (true) {
  final String line = reader.readLine();
  if (line.isEmpty()) {
    break;
  }
  System.out.println(line);
}

您将会看出以下音信:

GET / HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8,ru;q=0.6,uk;q=0.4

客商端(举例谷歌(Google卡塔尔的Chrome浏览器)把这一个文件传给已建构的连年。它总是本地的8080端口,只要连接变成,它会马上将那一个文件发给服务器,然后等待响应。

作者们的劳作正是用从呼吁获得的新闻创设相应的HTTP响应。如若大家的服务器非常原始,能够忽视央浼中的全数新闻而对富有的央求仅仅重临“Hello,
world!
”(轻巧起见本人用了IOUtils)。

import java.net.Socket;
import java.net.ServerSocket;
import org.apache.commons.io.IOUtils;
public class Foo {
  public static void main(final String... args) throws Exception {
    final ServerSocket server = new ServerSocket(8080);
    while (true) {
      try (final Socket socket = server.accept()) {
        IOUtils.copy(
          IOUtils.toInputStream("HTTP/1.1 200 OK/r/n/r/nHello, world!"),
          socket.getOutputStream()
        );
      }
    }
  }
}

正是那样。当服务器就绪,试着编写翻译它跑起来。让浏览器指向,
world!”。

$ javac -cp commons-io.jar Foo.java
$ java -cp commons-io.jar:. Foo &
$ curl http://localhost:8080 -v
* Rebuilt URL to: http://localhost:8080/
* Connected to localhost (::1) port 8080 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.37.1
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 200 OK
* no chunk, no close, no size. Assume close to signal end
<
* Closing connection 0
Hello, world!

那就是你编写翻译web服务器要做的有着业务。今后让大家来谈谈哪些让它面向对象並且可组件化。让我们看看Takes框架是什么树立的。

路由和视图函数

顾客端(举例 Web 浏览器卡塔尔国把供给发送给 Web 服务器, Web
服务器再把需要发送给 Flask app 实例。 app 实例须要明白对每一种 UPAJEROL
央浼运转哪些代码, 所以 app 实例保存了三个 UHighlanderL 到 Python
函数的照耀关系。处理 U逍客L 和函数之间关系的次序名称为路由。

在 Flask 中采纳 app 实例提供的 app.route
装饰器把所装修的函数注册为路由:

@app.route('/')
def index():
    return '<h1>Hello, 世界!</h1>'

装饰器是足以把函数注册为事件的管理程序。

开始是把 index(State of Qatar 函数注册为 app
根地址的管理程序。假诺计划的前后相继的服务器域名称叫
www.example.com,
在浏览器中访谈
http://www.example.com
后会触发服务器推行 index(State of Qatar 函数。那一个函数的归来值称为 响应,
它是顾客端接受到的内容。如若客商端是 Web 浏览器,
响应就是浮现给顾客看的文书档案。

像 index(卡塔尔国 那样的函数称之为 视图函数(view
function卡塔尔。视图函数再次来到的响应得以是富含 HTML
的简短字符串,也能够是叶影参差的表单。

可变 URL:

@app.route('/user/<name>')
def user(name):
    return '<h1>Hello, %s!</h1>' % name

路由中的动态部分默许使用字符串, 也能够利用 int/float/path 类型, path
类型也是字符串, 但不把斜线作为分割符, 而将其看作动态片段的一有的。

@app.route('/user/<int:id>')

1.1 HTTP

HTTP
RFC 2616 – Hypertext Transfer Protocol —
HTTP/1.1
版本: HTTP/1.1
TCP 连接
基于:“请求—响应”的协议

路由/分发

最珍视的一步是调整哪个人来承当构建HTTP响应。每一种HTTP央求都有1)四个查询,2)二个情势,3)一些头顶音信。要使用那多少个参数,供给实例化三个对象来为我们创设响应。在大部的Web框架中,那么些进度叫做央求分发或路由。上面是怎么着用Takes完成这一个。

final Take take = takes.route(request);
final Response response = take.act();

大概有两步。第一步从takes创立Take的实例,第二步从takes创立响应的实例。为何接受这种形式?主假诺为着分离权力和义务。Takes的实例担当分发央浼並且开始化精确的Take,Take的实例担当创造响应。

用Takes创立一个简短的运用,你应该创设三个类。首先,二个落到实处Takes接口的类:

import org.takes.Request;
import org.takes.Take;
import org.takes.Takes;
public final class TsFoo implements Takes {
  @Override
  public Take route(final Request request) {
    return new TkFoo();
  }
}

我们独家用Ts和Tk的前缀代表Takes和Take。第贰个你应该成立的类,三个达成Take接口的类:

import org.takes.Take;
import org.takes.Response;
import org.takes.rs.RsText;
public final class TkFoo implements Take {
  @Override
  public Response act() {
    return new RsText("Hello, world!");
  }
}

当今到运行服务器的时候了:

import org.takes.http.Exit;
import org.takes.http.FtBasic;
public class Foo {
  public static void main(final String... args) throws Exception {
    new FtBasic(new TsFoo(), 8080).start(Exit.NEVER);
  }
}

FtBasic类就是落实了地点表达过的和socket相像的操作。它在端口8080上运营一个劳务器端的socket,通过传给构造函数TsFoo实例来散发全数步向的一而再。它在二个极端循环中完成分发,用Exit实例每秒检查是不是是时候停止。明显,Exit.NEVE奇骏总是回到“请不要结束”。

起头服务器

app 实例使用 run 方法运转 Flask 集成的 Web 服务器:

if __name__ == '__main__':
    app.run(debug=True)

__name__ == '__main__' 确定保障了独有直接 实践这几个本羊时才运营 Web
服务器。如若这些剧本由其它脚本引进, 程序假定父级脚本会运转区别的服务器,
由此不会实施 app.run()

服务器运行后会步向轮询, 等待并管理乞求,
轮询会一贯运维,直到程序停止,举个例子按 Ctrl-C 键。

1.1.1 HTTP 请求

三个 HTTP 央浼富含以下三有些
* 央求方法 —— ULacrosseI(Uniform Resource Identifier, 统一财富标志符卡塔尔国
* 请求头
* 实体

POST /examples/default.jsp HTTP/1.1
Accept: text/plain; text/html
Accept-Language: en-gb
Connection: Keep-Alive
Host: Localhost
User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)
Content-Length: 33
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate

lastName=Franks&firstName=Michael

澳门新浦京8455com 1

http .png

HTTP 辅助的多种诉求方法

  1. GET
  2. POST
  3. HEAD
  4. OPTIONS
  5. PUT
  6. DELET
  7. TRACE

UQX56I(Uniform Resource Identifier, 统一财富标记符卡塔尔国 制订 Internet
财富的完整路线。 U路虎极光I
平日会被解释为相对于服务器根目录的相对路线,因此,它连接以 “/”
开始的。
U牧马人L(Uniform Resource Locator, 统一能源定位符卡塔尔(قطر‎ 实际上是 U科雷傲I 的一种档案的次序。

本子公约指明了当下乞求使用的 HTTP 合同的版本。

HTTP请求

今日让大家来打探一下达到TsFoo的HTTP需要内部都有何样,我们能从呼吁中收获怎样。下边是在Takes中定义的Request接口:

public interface Request {
  Iterable<String> head() throws IOException;
  InputStream body() throws IOException;
}

倡议分为两有个别:尾部和正文。依照RFC
2616中HTTP规范,尾部饱含用来开摆正文的空行前的装有的行。框架中有过多实用的伸手装饰器。比如,昂CoraqMethod能够支持从底部第一行取到方法名。

final String method = new RqMethod(request).method();

讴歌MDXqHref用来救助提取查询部分还要进行解析。举例,上面是一个倡议:

GET /user?id=123 HTTP/1.1
Host: www.example.com

代码将会领取获得“123”:

GET /user?id=123 HTTP/1.1
Host: www.example.com

ENVISIONqPrint可以赢得整个须求只怕正文,作为字符串打字与印刷出来:

final String body = new RqPrint(request).printBody();

此处的想法是保证需要接口轻松,并且用装饰器提供剖判呼吁的职能。每一个装饰器都十二分精美牢固,只用来成功一件事。全部这几个装饰器都在“org.takes.rq”包中。你或者曾经理解,“揽胜极光q”前缀代表呼吁(Request)。

三个整体的 app

from flask import Flask
app = Flask(__name__)

@app.route('/')
def index():
    return '<h1>Hello, 世界!</h1>'

@app.route('user/<name>')
def user(name):
    return '<h1>hello, %s!</h1>' % name

if __name__ == '__main__':
    app.run(debug=True)

开发银行这么些 app:

(venv) $ python hello.py
* Running on http://127.0.0.1:5000/
* Restarting with reloader

在浏览器中键入:

 http://localhost:5000/user/Dave

会显示:

<h1>Hello, Dave!</h1>


1.1.2 HTTP 响应

一个 HTTP 响应包蕴三某些

  • 协议——状态码——描述
  • 响应头
  • 相应实体段

HTTP/1.1 200 OK
Server: Microsoft-IIS/4.0
Data: Mon, 5 Jan 2004 13:13:33 GMT
Content-Length: 112

<html>
    <head>
        <title>HTTP Response Example</title>
    </head>
    <body>
        Welcome to Brainy Software
    </body>
</html>

澳门新浦京8455com 2

http-respond.jpg

状态码 200 表示诉求成功。
响应实体正文是一段 HTML 代码

先是个真正的Web应用

让我们创立大家第三个真正含义上的Web应用,它将会做一些有含义的专门的学问。笔者推荐以贰个Entry类起头。对Java来讲,从命令行运行一个使用是必需的。

import org.takes.http.Exit;
import org.takes.http.FtCLI;
public final class Entry {
  public static void main(final String... args) throws Exception {
    new FtCLI(new TsApp(), args).start(Exit.NEVER);
  }
}

本条类只含有三个静态 main(State of Qatar函数,从命令行运维应用时JVM将会调用这些艺术。如你所见,实例化 FtCLI,传进二个TsApp类的实例和命令行参数。大家将会立马创立TsApp对象。FtCLI(翻译成“front-end
with command line
interface”即“带命令行接口的前端”)创立了FtBasic的实例,用一些管用的装饰器对它进行李包裹装并依据命令行参数配置。比如,“–port=8080”将会转变到8080端口号并被视作
FtBasic 构造函数的第三个参数字传送入。

web应用本身世袭TsWrap,叫做TsApp:

import org.takes.Take;
import org.takes.Takes;
import org.takes.facets.fork.FkRegex;
import org.takes.facets.fork.TsFork;
import org.takes.ts.TsWrap;
import org.takes.ts.TsClasspath;
final class TsApp extends TsWrap {
  TsApp() {
    super(TsApp.make());
  }
  private static Takes make() {
    return new TsFork(
      new FkRegex("/robots.txt", ""),
      new FkRegex("/css/.*", new TsClasspath()),
      new FkRegex("/", new TkIndex())
    );
  }
}

咱俩将登时探究TsFork类。

若果你正在利用Maven,你应有从那么些pom.xml早先:

<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>foo</groupId>
  <artifactId>foo</artifactId>
  <version>1.0-SNAPSHOT</version>
  <dependencies>
    <dependency>
      <groupId>org.takes</groupId>
      <artifactId>takes</artifactId>
      <version>0.9</version> <!-- check the latest in Maven Central -->
    </dependency>
  </dependencies>
  <build>
    <finalName>foo</finalName>
    <plugins>
      <plugin>
        <artifactId>maven-dependency-plugin</artifactId>
        <executions>
          <execution>
            <goals>
              <goal>copy-dependencies</goal>
            </goals>
            <configuration>
              <outputDirectory>${project.build.directory}/deps</outputDirectory>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

运作“ mvn clean package”会在“target ”目录中生成二个 foo.jar
文件相同的时候在“target/deps”目录生成一群具备JA汉兰达正视包。以后您能够从命令行运营应用:

$ mvn clean package
$ java -Dfile.encoding=UTF-8 -cp ./target/foo.jar:./target/deps/* foo.Entry --port=8080

行使已经就绪,你能够布署到Heroku。在仓房的根目录下创造叁个Profile文件,然后把库房推入Heroku。下边是Profile的内容:

web: java -Dfile.encoding=UTF-8 -cp target/foo.jar:target/deps/* foo.Entry --port=${PORT}

伸手/响应循环

1.2 Socket 类

Socket (套接字卡塔尔(قطر‎ 是互联网连接的端点。Socket
使应用程序能够从互联网中读取数据,能够向互连网中写入数据。分裂计算机上的七个应用程序能够通过三番若干回发送或收到字节流,以此达到相互通讯的指标。为了从三个应用程序向另二个应用程序发送音信,需求明白另三个应用程序中
Socket 的 IP 地址和端口号。

public class Socket
澳门新浦京8455com,extends Object
implements Closeable
This class implements client sockets (also called just “sockets”). A
socket is an endpoint for communication between two machines.
The actual work of the socket is performed by an instance of the
SocketImpl class. An application, by changing the socket factory
that creates the socket implementation, can configure itself to create
sockets appropriate to the local firewall.

创制多个 Socket :

Socket() Creates an unconnected socket, with the system-default type of SocketImpl.
Socket(String host, int port) Creates a stream socket and connects it to the specified port number on the named host.
Socket(String host, int port, boolean stream) Deprecated. Use DatagramSocket instead for UDP transport.

host – the host name, or null for the loopback address.

port – the port number.

host:远程主机的名称或 IP 地址 (127.0.0.1 表示贰个地面主机卡塔尔
post:连接远程应用程序的端口号

采纳 socket 实例发送/接收字节流:
socket.getOutputStream()

发送文书
PrintWriter out = new PrintWriter(socket.getOutputStream(), true)

收受字节流:
socket.getInputStream()

TsFork

TsFork类看上去是中间贰个框架宗旨要素。它将跻身的HTTP央求路由到正确的“take”。它的逻辑非常的简短,代码也唯有为数非常的少行。它包裹了“forks”的叁个会见,“forks”是Fork<Take>接口的实例。

public interface Fork<T> {
  Iterator<T> route(Request req) throws IOException;
}

唯有的 route()方法重临空迭代器或许隐含单个take的迭代器。TsFork遍历全体的forks,调用它们的
route(卡塔尔方法直到个中八个回到take。一旦发生,TsFork会把那个take再次来到给调用者,即 FtBasic。

明天咱们休戚相关来创建二个简便的fork。举个例子,当呼吁USportageL“/status”时,大家想体现应用的情状。以下是代码达成:

final class TsApp extends TsWrap {
  private static Takes make() {
    return new TsFork(
      new Fork.AtTake() {
        @Override
        public Iterator<Take> route(Request req) {
          final Collection<Take> takes = new ArrayList<>(1);
          if (new RqHref(req).href().path().equals("/status")) {
            takes.add(new TkStatus());
          }
          return takes.iterator();
        }
      }
    );
  }
}

自家相信这里的逻辑是清楚的。要么回到一个空迭代器,要么重回内部含有TKStatus实例的迭代器。假如回到空迭代器,TsFork将尝试在汇聚中检索另三个那样的fork,它能够获取Take的实例进而举行响应。顺便提一下,要是什么也没察觉具备的forks重回空迭代器,那么TsFork将抛出“Page
not found”的可怜。

如此的逻辑通过叫做FkRegex的开箱即用fork完毕,尝试用提供的通用表达式去相称须要的UCRUISERI:

final class TsApp extends TsWrap {
  private static Takes make() {
    return new TsFork(
      new FkRegex("/status", new TkStatus())
    );
  }
}

小编们能够结合多层布局的TsFork类,比方:

final class TsApp extends TsWrap {
  private static Takes make() {
    return new TsFork(
      new FkRegex(
        "/status",
        new TsFork(
          new FkParams("f", "json", new TkStatusJSON()),
          new FkParams("f", "xml", new TkStatusXML())
        )
      )
    );
  }
}

Again, I believe it’s obvious. The instance of FkRegex will ask an
encapsulated instance of TsFork to return a take, and it will try to
fetch it from one that FkParams encapsulated. If the HTTP query is
/status?f=xml, an instance of TkStatusXML will be returned.

本人相信逻辑是很清楚的。FkRegex的实例将会必要TsFork的卷入实例再次回到三个take,並且它会尝试从FkParams封装的实例中收获。

app 和伸手上下文

Flask 从客户端收到央求时, 要让视图函数能访谈一些对象,
那样本领管理乞请。伸手对象装进了顾客端(比如浏览器卡塔尔国发送的 HTTP 央求。

要让视图函数能访谈伸手对象
八个明白的办法是把哀告对象作为参数字传送递给视图函数,
然则那会导致程序中各类视图函数都增加三个参数。要是视图函数还要访问别的对象,
那么视图函数会变得更其痴肥和难以有限支持。

为此, Flask 使用 上下文 临时把一些对象变成全局可访问:

from flask import request
@app.route('/')
def index():
    user_agent = request.headers.get('User-Agent')
    return '<p>你的浏览器是 %s</p>' % user_agent

在这里个事例中大家把 request 充作全局变量来行使。事实上, request
不或许是全局变量, 你动脑,
在四个线程同一时候管理不等客商端出殡的两样乞请时, 每种线程看见的 request
对象自然分化。 Flask 使用上下文让特定的变量在每四个线程中全局可访谈,
与此同有时候却不会振撼其余线程。

三十七线程 Web 服务器会创建四个线程池,
再从线程池中选用贰个线程用于拍卖选用到的央浼。

在 Flask 中有二种上下文: app 上下文
请求上下文。下表列出了这几种上下文提供的全局变量:

变量名 上下文 说明
current_app app上下文 当前所激活app的app实例
g app上下文 处理请求时用作临时存储的对象。每次请求都会重设这个变量
request 请求上下文 请求对象, 封装了客户端发出的 HTTP 请求中的内容
session 请求上下文 用户会话, 用于存储请求之间需要"记住"的值的字典

Flask 在散发央求在此之前激活(或推送卡塔尔app上下文和伸手上下文,
央求处理到位后再将其除去。 app 上下文在被推送后, 就足以在线程中选拔
current_appg 变量。肖似地, 在伏乞上下文被推送后, 就足以选取
requestsession 变量。假如我们接受这么些变量时并未有激活 app
上下文或必要上下文, 那么程序就能出错。

激活虚构境遇后步入 Python shell, 上面演示app上下文的选拔办法:

>>> from hello import app
>>> from flask import current_app
>>> current_app.name
...
RuntimeError: Working outside of application context.
>>> app_ctx = app.app_context()
>>> app_ctx.push() # 推送 app 上下文
>>> current_app.name
'hello'
>>> app_ctx.pop() # 弹出 app 上下文

在这里个事例中, 未有激活 app 上下文在此之前就调用 current_app.name
就能够形成错误, 不过推送完上下文之后就足以调用了。

专一, 在 app 实例上调用 .app_context(State of Qatar 方法便获得了一个程序上下文。

Example: 成立三个Socket,用于与本土 HTTP 服务器举办通讯


Socket socket = new Socket(host, portNumber);
OutputStream os = socket.getOutputStream();
boolean autoflush = true;
PrintWriter out = new PrintWriter(socket.getOutputStream(), autoflush);
String message = command.getText();
out.println(message);
out.println("Host: localhost:8080");
out.println("Connection: Close");
out.println();
BufferedReader in = new BufferedReader(
        new InputStreamReader(socket.getInputStream()));
boolean loop = true;
StringBuffer sb = new StringBuffer(8096);
    while(loop){
    if (in.ready()) {
        int i = 0;
        while (i != -1) {
            i = in.read();
            sb.append((char) i);
        }
        loop = false;
    }
    Thread.currentThread().sleep(50);
}
    response.setText(sb.toString());
    socket.close();

HTTP响应

于今让我们探讨HTTP响应的构造以致它的面向对象的肤浅——
Response。以下是接口的定义:

public interface Response {
  Iterable<String> head() throws IOException;
  InputStream body() throws IOException;
}

和Request看起来特别相近,是否?好吧,它是同一的。因为HTTP需要和响应的布局大约是一致的,独一的分裂只是首先行。有成都百货上千立竿见影的装饰器辅助创设响应。他们是组件化的,那使得应用起来十分有益。举个例子,假诺你想营造多个带有HTML页面包车型客车响应,你能够那样做:

final class TkIndex implements Take {
  @Override
  public Response act() {
    return new RsWithStatus(
      new RsWithType(
        new RsWithBody("<html>Hello, world!</html>"),
        "text/html"
      ),
      200
    );
  }
}

在此个示例中,HavalsWithBody装饰器创建响应的正文,不过未有头顶。然后冠道sWithType
给响应加多“ Content-Type:
text/html”底部。接着中华VsWithStatus确认保障响应的第一行李包裹括“HTTP/1.1 200 OK”。

您能够复用已部分装饰器来成立和煦的装饰器。能够看看 rultor.com
上 RsPage
如何自定义装饰器。

恳请调解

程序收到顾客端发来的号令时, 要找随地理该央浼的视图函数。Flask 通过在 app
的 U本田CR-VL 映射中查找央浼的 U冠道L 来完结那些职责。 UENCOREL 映射是 UHighlanderL
和视图函数之间的呼应关系。 Flask 使用 app.route 装饰器/非装饰器形式的
app.add_url_rule() 生成映射。

大家在 Python shell 中查阅 Flask 的 app
中映射是怎么样体统的:(全数操作请保管您曾经激活了设想情况)

(venv) $ python
>>> from hello import app
>>> app.url_map
>>> app.url_map
Map([<Rule '/' (HEAD, OPTIONS, GET) -> index>,
<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
<Rule '/user/<name>' (HEAD, OPTIONS, GET) -> user>])

//usr/<name> 路由在 app 中使用 app.route 装饰器定义。
/static/<filename> 路由是 Flask 增多的奇怪路由, 用于访问静态文件。

U卡宴L 映射中的 HEAD、OPTIONS、GET 是伏乞方法。Flask
为每种路由都钦点了须求方法, 那样不一样的央求发送到相近的 UGL450L 上时,
会使用分化的视图函数实行拍卖。 HEAD 和 OPTIONS 方法由 Flask 自动管理,
因而得以说地点的 app 中 U冠道L 映射中的 3 个路由都应用 GET 方法。

ServerSocket 类

Socket 类:表示贰个顾客端套接字。
ServerSocket 类:服务器套接字。

public class ServerSocket
extends Object
implements Closeable
This class implements server sockets. A server socket waits for
requests to come in over the network. It performs some operation based
on that request, and then possibly returns a result to the
requester.
The actual work of the server socket is performed by an instance of
the SocketImpl class. An application can change the socket factory
that creates the socket implementation to configure itself to create
sockets appropriate to the local firewall.

劳务器套接字要等待来自顾客端的连年要求。当服务器套接字接受到了连年诉求之后,它会创制三个Socket 实例来拍卖与顾客端的通讯。

ServerSocket 的布局函数

Constructor Description
ServerSocket() Creates an unbound server socket.
ServerSocket(int port) Creates a server socket, bound to the specified port.
ServerSocket(int port, int backlog) Creates a server socket and binds it to the specified local port number, with the specified backlog.
ServerSocket(int port, int backlog,InetAddress bindAddr) Create a server with the specified port, listen backlog, and local IP address to bind to.

port – the port number, or 0 to use a port number that is
automatically allocated.

backlog – requested maximum length of the queue of incoming
connections.

bindAddr – the local InetAddress the server will bind to

backlog:服务端socket管理客商端socket连接是亟需自然时间的。ServerSocket有七个体系,贮存还尚无来得及管理的顾客端Socket,那几个行列的容积便是backlog的含义。假诺队列已经被顾客端socket占满了,借使还会有新的连年过来,那么ServerSocket会推却新的总是。也正是说backlog提供了容积节制效用,制止太多的客商端socket占用太多服务器财富。

bindAddr:必须是 java.net.InetAddress. 类的实例

创建 InetAddress 实例对象的一种简易方法是调用其静态方法
getByName(),传入包涵主机名的字符串:

InetAddress.getByName("127.0.0.1");

创建了 ServerSocket
实例后,能够使其等待传入的接连央求,该央求会通过劳动器套接字侦听的端口上的绑定地址传入,这一个干活儿能够透过调用
ServerSocket 类的 accept
方法成功。独有收纳连接伏乞后,该办法才会回去。

public Socket accept() throws IOException

A new Socket s is created and, if there is a security manager, the
security manager’s checkAccept method is called with
s.getInetAddress().getHostAddress() and s.getPort() as its
arguments to ensure the operation is allowed. This could result in a
SecurityException.

Returns: the new Socket.

哪些使用模板?

如您所见,再次回到轻松的“Hello,
world”页面实际不是一个大主题材料。可是回到更复杂的输出例如HTML页面、XML文书档案、JSON数据集,又该怎么做?让我们从叁个简约的模板引擎“Velocity”初阶。好啊,其实它并不轻松。它一定刚劲,但是本身只提议在简洁明了境况下选取。下边是关于它怎么专门的工作:

final class TkIndex implements Take {
  @Override
  public Response act() {
    return new RsVelocity("Hello, ${name}")
      .with("name", "Jeffrey");
  }
}

RsVelocity 构造器选用Velocity模板作为独一参数。然后,你能够调用“with(卡塔尔(قطر‎”方法,往Velocity上下文注入数据。当到渲染HTTP响应的时候,PRADOsVelocity
将会将模板和布局的上下文实行“评估”。再度重申,小编只引用在特别轻巧的输出时行使这种模板情势。

对于更头晕目眩的HTML文书档案,作者将推举你利用结合Xembly使用XML/XSLT。在这里前的几篇博客中本身表达了这种主张,XML+XSLT
in a
Browser 和RESTful
API and a Web Site in the Same
URL。这种方式轻松强盛——用Java生成XML,XSLT
微机将其转变来HTML文书档案。那正是大家什么分离表示和数码。在MVC来看,XSL样式表是二个“视图”,TkIndex
是一个“调控器”。

不久笔者会单独写一篇小说来介绍使用Xembly和XSL模板生成页面。

况兼,我会在Takes框架中为
JSF/Facelets 和JSP 渲染创设装饰器。倘让你对这一部分工作感兴趣,请fork那个框架并付诸你的pull伏乞。

恳请钩子

神跡要求在乞求早先或现在实施代码会很有用。举个例子,
在乞求最初时大家兴许供给创建数据库连接/认证发起呼吁的客商。为了幸免在各类视图函数中都使用重复的代码,
Flask 提供了挂号通用函数的效能,
注册的函数可在号召被分发到视图函数早先/之后被调用。

伸手钩子 使用装饰器完结。 Flask 援救以下 4 种钩子:

  • before_first_request: 注册六个函数, 在拍卖第一个乞请此前运维。
  • before_request: 注册多少个函数, 在历次供给以前运营。
  • after_request: 注册一个函数, 若无未处理的老大抛出,
    则在历次央求之后运营。
  • teardown_request: 注册三个函数, 纵然有未管理的极其抛出,
    也在历次诉求之后运转。

在号令钩子和视图函数之间分享数据貌似采用上下文全局变量 g。 例如
before_request 管理程序能够从数据库中加载已登入顾客, 并将其保存到
g.user 中。随后调用视图函数时, 视图函数再利用 g.user 获取客商。

1.3 应用程序

本章的 Web 服务器满含三个类:

  • HttpServer
  • Request
  • Response
  1. 次第的入口点(静态 main()方法)在 HttpServer
    类中。main()方法创制二个 HttpServer 实例,然后调用其 await()
    方法。
  2. await()方法会在钦点端口上等待 HTTP
    需要,对其拍卖,然后发送响应新闻回想客端。在选取到关门命令前,它会维持等待景况。

该应用程序仅发送坐落于内定目录的静态财富央浼,如 HTML
文件和图像文件,它将盛传到的 HTTP
伏乞字节流现实到调整台上。然而它不发送任何 header (头音信卡塔尔(قطر‎到浏览器,如日期或 cookies 等。

何以长久化?

现今,多个主题材料就出去了。如哪管理诸如数据库、内部存储器布局、网络连接之类的悠久层实体?笔者的提出是在Entry类中实例化它们,并把它们当作参数字传送入TsApp的构造函数中。然后,TsApp将会把它们传播自定义的“takes”的构造函数中。

比方说,大家有贰个PostgreSQL数据库,包括部分用来渲染的表数据。这里小编将要Entry类中实例化数据库连接(使用 BoneCP连接池):

public final class Entry {
  public static void main(final String... args) throws Exception {
    new FtCLI(new TsApp(Entry.postgres()), args).start(Exit.NEVER);
  }
  private static Source postgres() {
    final BoneCPDataSource src = new BoneCPDataSource();
    src.setDriverClass("org.postgresql.Driver");
    src.setJdbcUrl("jdbc:postgresql://localhost/db");
    src.setUser("root");
    src.setPassword("super-secret-password");
    return src;
  }
}

后天,TsApp的构造器必需接受三个“java.sql.Source”类型的参数:

final class TsApp extends TsWrap {
  TsApp(final Source source) {
    super(TsApp.make(source));
  }
  private static Takes make(final Source source) {
    return new TsFork(
      new FkRegex("/", new TkIndex(source))
    );
  }
}

TkIndex
类相符接收三个Source类型的参数。为了取SQL表数据并把它调换到HTML,相信您驾驭TkIndex内部如哪个地方理的。这里的关键点是在行使(TsApp类的实例)初步化时必须注入注重。那是自始自终干净的借助注入机制,完全没有必要任何容器。更加的多相关阅读请参阅“Dependency
Injection Containers Are Code
Polluters”。

响应

Flask 调用视图函数后, 会将其重临值作为响应的剧情。大许多景观下,
响应正是三个简约的字符串, 作为 HTML 页面回送顾客端。

在响应的文本之后增加叁个状态码:

@app.route('/')
def index():
    return '<h1>Bad Request</h1>', 400

代表央浼无效。

视图函数再次来到的响应还足以选拔第七个参数,
那是一个由首部(headerState of Qatar组成的词典, 能够增加到 HTTP 响应中。

Flask 还足以回来 Response 对象。 make_response() 函数可担任 三分一/3
个参数(和视图函数的再次回到值同样卡塔尔国, 并重回叁个 Response 对象。

from flask import make_response
@app.route('/')
def index():
    response = make_response('<h1>This document carries a cookie!</h1>')
    response.set_cookie('answer', '42')
    return response

重定向是一种特有的响应类型。这种响应类型未有页面文书档案,
只告诉浏览器二个新的地点用于加载新页面。Flask 已经提供了 redirect()
函数:

from flask import redirect
@app.route('/')
def index():
    return redirect('http://www.example.com')

再有一种奇特的 abort 响应, 用于管理错误:

from flask import abort
@app.route('/user/<id>')
def get_user(id):
    user = load_user(id):
    if not user:
        abort(404)
    return '<h1>Hello, %s!</h1>' % user.name

1.3.1 HttpServer

package ch1;
import java.io.IOException;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;


// 处理对指定目录中静态资源的请求
public class HttpServer {

    // 指明目录
    public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot";

    //目录名为 'webroot'


    // shutdown command
    private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
    // 关闭命令——/SHUTDOWN

    // the shutdown command received
    private boolean shutdown = false;

    public static void main(String[] args) {
        HttpServer server = new HttpServer();
        server.await();
    }

    public void await() {

        ServerSocket serverSocket = null;
        int port = 8080;

        try {
            serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }

        //Loop waiting for a request
        while (!shutdown) {
            Socket socket = null;
            InputStream input = null;
            OutputStream output = null;

            try {
                // accept()
                // Listens for a connection to be made to this socket
                // and accepts it. The method blocks until a connection is made.
                socket = serverSocket.accept();

                // getInputStream()
                // an input stream for reading bytes from this socket.
                input = socket.getInputStream();

                // getOutputStream()
                // an output stream for writing bytes to this socket.
                output = socket.getOutputStream();

                // creat Request object and parse
                Request request = new Request(input);
                request.parse();

                // creat Response object
                Response response = new Response(output);
                response.setRequest(request);
                response.sendStaticResource();

                // close the socket
                socket.close();

                // check if the prevous URI is a shutdown command
                shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
                // 判断一个输入元素与是否与已知元素相同

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

其一 Web
服务器能够拍卖对点名目录中的静态财富的伏乞。该目录包罗国有静态变量
final WEB_ROOT 指明的目录及其全部子目录。WEB_ROOT 的开头值为:

public static final String WEB_ROOT = 
    System.getProperty("user.dir") + File.separator + "webroot";

若要供给静态财富,能够在浏览器之处栏或 URL 框中输入如下的 URL

http://machineName:port/staticResource

http://localhost:8080/source

若从另一台微计算机上向该应用程序发出伏乞,则 machineName
是应用程序所在计算机的称谓或 IP
地址;若在平等台机器上发出的伸手,则能够将 machineName 替换为
localhost

此一而再诉求使用的端口为 8080。

staticResourse 是乞求的文书的名字,该文件必需放在 WEB_ROOT
指向的目录下。

若要关闭服务器,能够因此 Web 浏览器之处栏或 U帕杰罗I 框,在 U奇骏I 的
host:port 部分前面输入预先定义好的字符串,从 WEB
浏览器发送一条关闭命令,那样服务器就能够接到关闭命令了。

例中的关闭命令定义在 HttpServer 类的 SHUTDOWN 静态 final 变量中:

private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";

若要关闭服务器,输入

http://localhost:8080/SHUTDOWN

await() 方法并不是 wait() 方法,是因为 wait() 方法是
java.lang.Object 类中与应用线程相关的尤为重要措施。

  1. await() 方法先创设贰个 ServerSocket 实例,然后踏入二个 while
    循环,从 8080 端口接受到 Http 请求后, await() 方法会从
    accept() 方法再次回到的 Socket 实例中,获取 java.io.InputStream
    java.io.OutputStream 对象:

    input = socket.getInputStream();
    output = socket.getOutputStream();
    
  2. 之后,await() 方法制造叁个 Request 对象,并调用其 paser()
    方法来剖析 HTTP 央浼的本来面目数据:

//create Request object and parse
Request request = new Request(input);
request.parse(); 
  1. 然后,await() 方法会创立二个 Response 对象,并分别调用其
    setRequest() 方法和 sendStaticResource() 方法:

    // creat Response object
    Response response = new Response(output);
    response.setRequest(request);
    response.sendStaticResource();
    
  2. 最后,await() 方法关闭套接字,调用 Request 类的 getUri()
    方法来测量检验 HTTP 请求的 URI 是或不是是关闭命令,若是,则将变量
    shutdown 设置为 true,程序退出 while 循环。

    // close the socket
    socket.close();
    
    // check if the prevous URI is a shutdown command
    shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
    

单元测量试验

因为种种类是不可变的同有时间有所的依据都以由此布局函数注入,所以单元测量试验特别容易。比方大家想测验“TkStatus”,假定它将会回到四个HTML响应(笔者动用JUnit
4 和Hamcrest):

import org.junit.Test;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
public final class TkIndexTest {
  @Test
  public void returnsHtmlPage() throws Exception {
    MatcherAssert.assertThat(
      new RsPrint(
        new TkStatus().act()
      ).printBody(),
      Matchers.equalsTo("<html>Hello, world!</html>")
    );
  }
}

长久以来,大家得以在一个测量试验HTTP服务器中运维全套应用也许其余四个独自的“take”,然后经过忠实的TCP套接字测验它的行事;举例(我使用jcabi-http布局HTTP乞求并且检查实验输出):

public final class TkIndexTest {
  @Test
  public void returnsHtmlPage() throws Exception {
    new FtRemote(new TsFixed(new TkIndex())).exec(
      new FtRemote.Script() {
        @Override
        public void exec(final URI home) throws IOException {
          new JdkRequest(home)
            .fetch()
            .as(RestResponse.class)
            .assertStatus(HttpURLConnection.HTTP_OK)
            .assertBody(Matchers.containsString("Hello, world!"));
        }
      }
    );
  }
}

FtRemote在自由的TCP端口运行多个测验Web服务器,何况在
FtRemote.Script 提供的实例中调用 exec(卡塔尔国方法。此情势的率先个参数是刚刚启动的web服务器主页面包车型大巴U汉兰达I。

Takes框架的布局非常模块化且易于组合。任何独立的“take”都可以看成多个单身的构件被测验,相对独立于框架和其余“takes”。

Flask 扩展

1.3.2 Request

Request 类表示一个 HTTP 乞求,能够传递 InputStream
对象(从通过管理与客商端通讯的 Socket 对象中获得的),来创设 Request
对象。能够调用 InputStream 对象中的 read() 方法来读取 HTTP
央浼的原有数据。

package ch1;

import java.io.IOException;
import java.io.InputStream;

public class Request {
    private InputStream input;
    private String uri;

    public Request(InputStream input) {
        this.input = input;
    }

    // 解析 HTTP 请求的原始数据
    public void parse() {
        // read a set of characters from the socket
        StringBuffer request = new StringBuffer(2048);
        int i;
        byte[] buffer = new byte[2048];
        try {
            i = input.read(buffer);

        } catch (IOException e) {
            e.printStackTrace();
            i = -1;
        }

        for (int j = 0; j < i; j++) {
            request.append((char) buffer[j]);
        }

        System.out.print(request.toString());
        uri = parseUri(request.toString());
    }

    private String parseUri(String requestString) {
        int index1, index2;
        index1 = requestString.indexOf(' ');
        if (index1 != -1) {
            index2 = requestString.indexOf(' ', index1 + 1);
            if (index2 > index1)
                return requestString.substring(index1 + 1, index2);
        }
        return null;
    }

    public String getUri() {
        return uri;
    }

}

Request

parse():解析 HTTP 要求中的原始数据。通过调用 private 方法
parseUri() 来解析 HTTP 请求的 URI

parseUri(String requestString):将 URI 存款和储蓄在变量 uri 中。

getUri():返回 HTTP 请求的 URI

  1. parse() 方法从传出到 Request 对象中的套接字的 InputStream
    对象中读取整个字节流,并将字节数据存款和储蓄在缓冲区中。然后接纳缓冲区字节数组中的数组填充
    StringBuffer 对象 request ,并将 StringBufferString表示
    传递给 parseUri() 方法。

    public void parse() {
            // read a set of characters from the socket
            StringBuffer request = new StringBuffer(2048);
            int i;
            byte[] buffer = new byte[2048];
            try {
                i = input.read(buffer);
    
            } catch (IOException e) {
                e.printStackTrace();
                i = -1;
            }
    
            for (int j = 0; j < i; j++) {
                request.append((char) buffer[j]);
            }
    
            System.out.print(request.toString());
            uri = parseUri(request.toString());
        }
    
  2. parseURI() 方法从号召行中拿走 URI

    private String parseUri(String requestString) {
            int index1, index2;
            index1 = requestString.indexOf(' ');
            if (index1 != -1) {
                index2 = requestString.indexOf(' ', index1 + 1);
                if (index2 > index1)
                    return requestString.substring(index1 + 1, index2);
            }
            return null;
        }
    

为啥叫这么些名字?

那是自己听见最频仍的主题材料。主见很简短,它和影视有关。当成立一部电影时,专业人士为了捕捉现实会拍录过多镜头然后放入电影中。每一个照相称作二个镜头(take)。

换句话说,叁个画面就像是现实的四个快速照相。每四个画面实例代表一按时刻的二个实际。那几个事实然后以响应的款型发送给顾客。

相通的道理也适用于框架。每一个Take实例都意味着一定有些时刻的实在存在。这些新闻会以Response方式发送。

运用Flask-Script援助命令行选项

Flask 的费用 Web 服务器辅助广大起动设置选项,但不能不在本子中作为参数字传送给
app.run()函数。这种措施并不足够有利,传递设置选项的优秀方式是行职务令行参数。

Flask-Script 是多个 Flask 扩充,为 Flask
程序加多了八个限令行深入分析器。Flask-Script
自带了一组常用选项,何况还协助自定义命令。

# 安装
(venv) $ pip install flask-script

要采纳 flask-script 必要在 hello.py 修正下程序:

from flask import Flask
from flask.ext.script import Manager

app = Flask(__name__)
manager = Manager(app)

@app.route('/')
def index():
    return '<h1>Hello,World</h1>'

if __name__ == '__main__':
    manager.run()

专为 Flask 开采的恢宏都暴漏在 flask.ext 命名空间下。Flask-Script
输出了三个名叫Manager 的类,可从 flask.ext.script 中引进。

以此增添的初叶化方法也适用于别的超多扩大:把 app
实例作为参数字传送给扩展的布局函数,早先化主类的实例。成立的对象足以在依次增加中应用。在此,服务器由
manager.run() 运行,运营后就可以解析命令行了。

在意, 在 Python 3 中要如此导入 flask-script 扩大, from
flask_script import Manager

今昔运作 hello.py,会展现多个相助消息:

$ python hello.py
usage: hello.py [-h] {shell,runserver} ...

positional arguments:
  {shell,runserver}
      shell           在 Flask 应用上下文中运行 Python shell
      runserver  运行 Flask 开发服务器:app.run()

optional arguments:
  -h, --help       显示帮助信息并退出

shell 命令用于在程序的上下文中运转 Python shell
会话。你能够动用那几个会话中运作维护职务或测验,还可调整非凡。看名就能够猜到其意义,
runserver 命令用来运维 Web 服务器。运维 python hello.py runserver
将以调节和测量检验形式运转 Web 服务器,可是我们还应该有大多精选可用:

$ python hello.py runserver --help
usage: hello.py runserver [-?] [-h HOST] [-p PORT] [--threaded]
                          [--processes PROCESSES] [--passthrough-errors] [-d]
                          [-D] [-r] [-R]

--host 参数是个很有用的选项,它报告 Web
服务器在哪些互联网接口上监听来自客商端的连续几日。暗中认可情形下,Flask 开拓 Web
服务器监听 localhost
上的连接,所以只采取来自服务器所在计算机发起的连天。下述命令让 Web
服务器监听公共互联网接口上的连年,允许同网中的其余Computer连接服务器:

(venv) $ python hello.py runserver --host 0.0.0.0
* Running on http://0.0.0.0:5000/
* Restarting with reloader

前几天,Web 服务器可应用 http://a.b.c.d:5000/
互连网中的任一台Computer进行探访,此中 “a.b.c.d” 是服务器所在Computer的外网 IP
地址。

1.3.3 Response

Response 类表示 HTTP 响应。

package ch1;

import java.io.*;

public class Response {
    private static final int BUFFER_SIZE = 2048;
    Request request;
    OutputStream output;

    public Response(OutputStream output) {
        this.output = output;
    }

    public void setRequest(Request request) {
        this.request = request;
    }

    public void sendStaticResource() throws IOException {

        byte[] bytes = new byte[BUFFER_SIZE];
        FileInputStream fis = null;

        try {
            File file = new File(HttpServer.WEB_ROOT, request.getUri());
            if (file.exists()){
                fis = new FileInputStream(file);
                int ch = fis.read(bytes, 0, BUFFER_SIZE);

                String msg = "HTTP/1.1 404 File Not Foundrn" +
                        "Content-Type: text/htmlrn" +
                        "rn";
                output.write(msg.getBytes());

                while (ch != -1){
                    output.write(bytes, 0, ch);
                    ch = fis.read(bytes, 0, BUFFER_SIZE);
                }
            }
            else {
                // find not found
                String errorMessage = "HTTP/1.1 404 File Not Foundrn" +
                        "Content-Type: text/htmlrn" +
                        "Content-Length: 23rn" +
                        "rn" +
                        "<h1>File Not Found</h1>";
                output.write(errorMessage.getBytes());
            }

        }catch (Exception e){
            // thrown if cannot instantiate a File object
            System.out.println(e.toString());
        }
        finally {
            if (fis != null ){
                fis.close();
            }
        }

    }
}
  1. Response 类的构造函数接纳三个 java.io.OutputStream 对象,:

        public Response(OutputStream output) {
            this.output = output;
        }
    
  2. Response 对象在 HttpServer 类的 await()
    方法中,通过传播从套接字中得到的 OutputStream 来创建。

  3. Response 内有多个 public 方法:setRequest()
    sendStaticResourse()setRequest() 方法会接收四个 Request
    对象作为参数。

  4. sendStaticResource() 方法用于发丝绸之路过叁个静态财富到浏览器,如
    HTML 文件。

    1. 它首先会经过传播父路径和子路线到 File 类的布局函数中来实例化
      java.io.File 类:

      File file = new File(HttpServer.WEB_ROOT, request.getUri());
      
    2. 下一场,它检查该文件是不是留存。

    3. 若存在,sedStaticResource() 方法会使用 File 对象创造
      java.io.InputStream 。然后它调用 FileInputStream 类的
      read() 方法,并将字节数据写入到 OutputStream
      输出中。(这种情况下,静态能源的故事情节作为土生土养数据发送到浏览器的):

      if (file.exists()){
          fis = new FileInputStream(file);
          int ch = fis.read(bytes, 0, BUFFER_SIZE);
      
          // mac 防止出错
          String msg = "HTTP/1.1 404 File Not Foundrn" +
                  "Content-Type: text/htmlrn" +
                  "rn";
          output.write(msg.getBytes());
      
          while (ch != -1){
              output.write(bytes, 0, ch);
              ch = fis.read(bytes, 0, BUFFER_SIZE);
          }
      }
      
    4. 若一纸空文,staticResource() 会发送错误音信到浏览器:

       else {
          // find not found
          String errorMessage = "HTTP/1.1 404 File Not Foundrn" +
                  "Content-Type: text/htmlrn" +
                  "Content-Length: 23rn" +
                  "rn" +
                  "<h1>File Not Found</h1>";
          output.write(errorMessage.getBytes());
      }
      

1.3.5 运维应用程序

  1. 执行 ch1 包

  2. 在地方栏或 URL 框输入:

    http://localhost:8080/source.html

Result

GET /source.html HTTP/1.1
Host: localhost:8080
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Upgrade-Insecure-Requests: 1
Cookie: __utma=111872281.722673633.1521287843.1521287843.1521287843.1; __utmz=111872281.1521287843.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); Idea-7e6930cd=8b83b26b-bd8b-480c-832b-3c0b6e5d4c39
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/604.5.6 (KHTML, like Gecko) Version/11.0.3 Safari/604.5.6
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
Connection: keep-alive

GET /favicon.ico HTTP/1.1
Host: localhost:8080
Accept: */*
Connection: keep-alive
Cookie: __utma=111872281.722673633.1521287843.1521287843.1521287843.1; __utmz=111872281.1521287843.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); Idea-7e6930cd=8b83b26b-bd8b-480c-832b-3c0b6e5d4c39
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/604.5.6 (KHTML, like Gecko) Version/11.0.3 Safari/604.5.6
Accept-Language: zh-cn
Referer: http://localhost:8080/source.html
Accept-Encoding: gzip, deflate

澳门新浦京8455com 3

ch1_result.png

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图