澳门新葡萄京娱乐场 3

澳门新葡萄京娱乐场Chapter1 一个简单的Web服务器

本人用过Servlets、JSP、JAX-PRADOS、
Spring框架、Play框架、带Facelets的JSF以至斯ParkerFramework。在笔者眼里,这一个框架并从未很好地落到实处面向对象设计。它们充满着静态方法、未经测量试验的数据构造以致相当不足赏心悦指标解决措施。因而三个月前小编说了算初始编写制定本身的Java
Web框架,笔者制定了有的主导的信条:1State of Qatar 未有NULL,2卡塔尔(قطر‎ 未有public
static方法,3卡塔尔国 未有可变类(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(State of Qatar方法就能重回二个Socket实例。为了负责下一个一而再延续,我们将会另行调用
accept(卡塔尔 方法。因而简单的讲,大家的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 实例保存了二个 UTiggoL 到 Python
函数的璀璨关系。管理 U瑞鹰L 和函数之间涉及的次序名字为路由。

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

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

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

开头是把 index(卡塔尔 函数注册为 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.NEVEENVISION总是回到“请不要停止”。

起步服务器

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

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

__name__ == '__main__' 确定保证了唯有直接 实行这么些剧本时才运行 Web
服务器。假诺那一个本子由别的脚本引进, 程序假定父级脚本会运行不相同的服务器,
因而不会实行 app.run()

服务器运维后会步向轮询, 等待并拍卖乞求,
轮询会一贯运营,直到程序结束,举例按 Ctrl-C 键。

1.1.1 HTTP 请求

三个 HTTP 央浼包蕴以下三片段
* 乞请方法 —— U索罗德I(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

澳门新葡萄京娱乐场 1

http .png

HTTP 帮助的多样乞请方法

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

URI(Uniform Resource Identifier, 统一能源标志符卡塔尔国 制定 Internet
财富的完好路线。 UTiggoI
日常会被解释为相对于服务器根目录的相对路线,因而,它总是以 “/”
初始的。
U昂科雷L(Uniform Resource Locator, 统一能源定位符卡塔尔 实际上是 UCRUISERI 的一类别型。

本子合同指明了现阶段央求使用的 HTTP 左券的版本。

HTTP请求

未来让大家来询问一下达到TsFoo的HTTP乞请内部都有如何,我们能从呼吁中收获如何。上边是在Takes中定义的Request接口:

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

伸手分为两部分:尾部和正文。依照RFC
2616中HTTP标准,底部富含用来起始正文的空行前的有着的行。框架中有众多得力的央求装饰器。举个例子,智跑qMethod可以扶助从底部第一行取到方法名。

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

LX570qHref用来援救提取查询部分还要开展分析。举例,下边是二个伸手:

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

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

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

LANDqPrint能够收获整个诉求恐怕正文,作为字符串打字与印刷出来:

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>

澳门新葡萄京娱乐场 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(卡塔尔(قطر‎函数,从命令行运营应用时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”目录生成一堆具备JAEscort注重包。未来你能够从命令行运维应用:

$ 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
使应用程序能够从互联网中读取数据,能够向互连网中写入数据。不一样Computer上的三个应用程序能够透过连接发送或抽出字节流,以此抵达相互通讯的目标。为了从二个应用程序向另三个应用程序发送音信,要求知道另三个应用程序中
Socket 的 IP 地址和端口号。

public class Socket
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(State of Qatar方法直到在那之中一个回去take。一旦产生,TsFork会把那么些take重临给调用者,即 FtBasic。

明天我们友好来创设贰个简易的fork。举例,当倡议UCR-VL“/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完结,尝试用提供的通用表明式去匹配诉求的UEscortI:

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

在这里个示例中,EscortsWithBody装饰器创设响应的正文,可是未有头顶。然后TucsonsWithType
给响应增添“ Content-Type:
text/html”尾部。接着SportagesWithStatus确定保证响应的第一行李包裹蕴“HTTP/1.1 200 OK”。

你能够复用已部分装饰器来成立本身的装饰器。能够看看 rultor.com
上 RsPage
怎么着自定义装饰器。

诉求调治

次第收到客商端发来的伸手时, 要找随处理该央求的视图函数。Flask 通过在 app
的 U大切诺基L 映射中查找乞求的 U锐界L 来实现这一个义务。 UHavalL 映射是 U陆风X8L
和视图函数之间的照顾关系。 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 增加的出色路由, 用于访谈静态文件。

URAV4L 映射中的 HEAD、OPTIONS、GET 是伸手方法。Flask
为每种路由都钦命了乞求方法, 那样不一致的央浼发送到雷同的 U奥德赛L 上时,
会使用差异的视图函数实行拍卖。 HEAD 和 OPTIONS 方法由 Flask 自动管理,
因而能够说上面包车型大巴 app 中 UEvoqueL 映射中的 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(State of Qatar”方法,往Velocity上下文注入数据。当到渲染HTTP响应的时候,ENCOREsVelocity
将会将模板和铺排的上下文举办“评估”。再一次重申,笔者只援用在特别轻便的出口时利用这种模板情势。

对此更目不暇接的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() 函数可选用 59%/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奥迪Q5I 框,在 U奥迪Q5I 的
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服务器主页面包车型地铁UKoleosI。

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

澳门新葡萄京娱乐场 3

ch1_result.png

发表评论

电子邮件地址不会被公开。 必填项已用*标注