1: <?php
2: /*
3: * Copyright (c) 2015 @trashtoy
4: * https://github.com/trashtoy/
5: *
6: * Permission is hereby granted, free of charge, to any person obtaining a copy of
7: * this software and associated documentation files (the "Software"), to deal in
8: * the Software without restriction, including without limitation the rights to use,
9: * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
10: * Software, and to permit persons to whom the Software is furnished to do so,
11: * subject to the following conditions:
12: *
13: * The above copyright notice and this permission notice shall be included in all
14: * copies or substantial portions of the Software.
15: *
16: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17: * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18: * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19: * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20: * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21: * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22: */
23: /**
24: * PHP class file.
25: * @auhtor trashtoy
26: * @since 2.2.0
27: */
28: namespace Peach\Http;
29:
30: use Peach\Http\Header\Raw;
31: use Peach\Http\Header\Status;
32: use Peach\Util\Strings;
33:
34: /**
35: * 一般的な WEB アプリケーションで使用されることを想定した Endpoint です.
36: *
37: * このクラスは $_GET, $_POST, $_SERVER などの情報を基にして Request を構築します.
38: * send() メソッドでは Response の内容を
39: * {@link http://php.net/manual/function.header.php header()} および
40: * {@link http://php.net/manual/function.echo.php echo()} を利用してクライアントに送信します.
41: */
42: class DefaultEndpoint implements Endpoint
43: {
44: /**
45: * クライアントから受信した HTTP リクエストです.
46: * createRequest() の返り値をキャッシュするために利用されます.
47: *
48: * @var Request
49: */
50: private $request;
51:
52: /**
53: * 新しい DefaultEndpoint インスタンスを構築します.
54: */
55: public function __construct()
56: {
57: }
58:
59: /**
60: * グローバル変数 $_GET, $_POST, $_SERVER などを基にして
61: * Request オブジェクトを作成します.
62: *
63: * @return Request
64: */
65: private function createRequest()
66: {
67: $keys = array_keys($_SERVER);
68: $request = new Request();
69: foreach ($keys as $key) {
70: if (!Strings::startsWith($key, "HTTP_")) {
71: continue;
72: }
73: $name = str_replace("_", "-", substr($key, 5));
74: $value = $_SERVER[$key];
75: $request->setHeader(Util::parseHeader($name, $value));
76: }
77:
78: $method = strtolower($_SERVER["REQUEST_METHOD"]);
79: $rawPath = $_SERVER["REQUEST_URI"];
80: $scheme = isset($_SERVER["HTTPS"]) ? "https" : "http";
81: $request->setQuery($_GET);
82: $request->setPost($_POST);
83: $request->setHeader(new Raw(":path", $rawPath));
84: $request->setHeader(new Raw(":scheme", $scheme));
85: $request->setHeader(new Raw(":method", $method));
86: $request->setPath($this->getRequestPath($rawPath));
87: return $request;
88: }
89:
90: /**
91: * URL からクエリ部分を除いたパスを返します.
92: * 無効なパスが指定された場合は代替値として "/" を返します.
93: *
94: * @param string $rawPath
95: * @return string クエリ部分を除いたパス文字列
96: */
97: private function getRequestPath($rawPath)
98: {
99: $path = "([^?#]+)";
100: $query = "(\\?[^#]*)?";
101: $fragment = "(\\#.*)?";
102: $matched = array();
103: return (preg_match("/\\A{$path}{$query}{$fragment}\\z/", $rawPath, $matched)) ? $matched[1] : "/";
104: }
105:
106: /**
107: * 受信した HTTP リクエストを $_GET, $_POST, $_SERVER などのグローバル変数を参照して
108: * Request オブジェクトに変換し, その結果を返します.
109: *
110: * @return Request
111: */
112: public function getRequest()
113: {
114: if ($this->request === null) {
115: $this->request = $this->createRequest();
116: }
117: return $this->request;
118: }
119:
120: /**
121: * 指定された Response オブジェクトを header() や echo()
122: * などを使ってクライアントに送信します.
123: *
124: * @param Response $response 送信対象の Response オブジェクト
125: */
126: public function send(Response $response)
127: {
128: $body = $response->getBody();
129: if ($body instanceof Body) {
130: $renderer = $body->getRenderer();
131: $value = $body->getValue();
132: $result = $renderer->render($value);
133: $response->setHeader(new Raw("Content-Length", strlen($result)));
134: } else {
135: $result = null;
136: }
137:
138: foreach ($response->getHeaderList() as $header) {
139: if ($header instanceof Status) {
140: $code = $header->getCode();
141: $reasonPhrase = $header->getReasonPhrase();
142: header("HTTP/1.1 {$code} {$reasonPhrase}");
143: continue;
144: }
145: $name = $header->getName();
146: $value = $header->format();
147: $this->printHeader($name, $value);
148: }
149: if (strlen($result)) {
150: echo $result;
151: }
152: }
153:
154: /**
155: * 指定されたヘッダー名およびヘッダー値の組み合わせを出力します.
156: * もしもヘッダー値が配列の場合 (Set-Cookie など) は,
157: * 同じヘッダー名による複数のヘッダー行を出力します.
158: *
159: * @param string $name ヘッダー名
160: * @param string|array $value ヘッダー値
161: */
162: private function printHeader($name, $value)
163: {
164: if (is_array($value)) {
165: foreach ($value as $item) {
166: header("{$name}: {$item}", false);
167: }
168: return;
169: }
170:
171: header("{$name}: {$value}");
172: }
173: }
174: