Merge: doc: fixed some typos and other misc. corrections
[nit.git] / lib / nitcorn / http_response.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2013 Frederic Sevillano
4 # Copyright 2013 Jean-Philippe Caissy <jpcaissy@piji.ca>
5 # Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
6 #
7 # Licensed under the Apache License, Version 2.0 (the "License");
8 # you may not use this file except in compliance with the License.
9 # You may obtain a copy of the License at
10 #
11 # http://www.apache.org/licenses/LICENSE-2.0
12 #
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
18
19 # Provides the `HttpResponse` class and `http_status_codes`
20 module http_response
21
22 import serialization
23 private import template
24
25 # A response to send over HTTP
26 class HttpResponse
27 serialize
28
29 # HTTP protocol version
30 var http_version = "HTTP/1.0" is writable
31
32 # Status code of this response (200, 404, etc.)
33 var status_code: Int is writable
34
35 # Return the message associated to `status_code`
36 fun status_message: nullable String do return http_status_codes[status_code]
37
38 # Headers of this response as a `Map`
39 var header = new HashMap[String, String]
40
41 # Body of this response
42 var body: Writable = "" is writable
43
44 # Files appended after `body`
45 var files = new Array[String]
46
47 # Finalize this response before sending it over HTTP
48 fun finalize
49 do
50 # Set the content length if not already set
51 if not header.keys.has("Content-Length") then
52 # Size of the body
53 var len
54 var body = self.body
55 if body isa Text then
56 len = body.byte_length
57 else if body isa Bytes then
58 len = body.length
59 else
60 # We need the length, but there is no length in a writable.
61 # So just render it as a bytes then measure :/
62 body = body.write_to_bytes
63 len = body.length
64 # Keep the body as bytes since we have it
65 self.body = body
66 end
67
68 # Size of included files
69 for path in files do
70 # TODO handle these error cases elsewhere, an error here will result in an invalid response
71 if not path.file_exists then
72 print_error "File does not exists at '{path}'"
73 continue
74 end
75
76 var stat = path.file_stat
77 if stat == null then
78 print_error "Failed to stat file at '{path}'"
79 continue
80 end
81
82 len += stat.size
83 end
84
85 # Set header
86 header["Content-Length"] = len.to_s
87 end
88
89 # Set server ID
90 if not header.keys.has("Server") then header["Server"] = "nitcorn"
91 end
92
93 # Get this reponse as a string according to HTTP protocol
94 fun render: Writable
95 do
96 finalize
97
98 var buf = new Template
99 buf.add("{http_version} {status_code} {status_message or else ""}\r\n")
100 for key, value in header do
101 buf.add("{key}: {value}\r\n")
102 end
103 buf.add("\r\n")
104 buf.add body
105 return buf
106 end
107 end
108
109 # Helper class to associate HTTP status code to their message
110 #
111 # You probably want the default instance available as the top-level method
112 # `http_status_codes`.
113 class HttpStatusCodes
114
115 # All know code and their message
116 var codes = new HashMap[Int, String]
117
118 # Init the status `codes` list.
119 protected init is old_style_init do insert_status_codes
120
121 # Get the message associated to the status `code`, return `null` in unknown
122 fun [](code: Int): nullable String
123 do
124 if codes.keys.has(code) then
125 return codes[code]
126 else return null
127 end
128
129 private fun insert_status_codes
130 do
131 codes[100] = "Continue"
132 codes[101] = "Switching Protocols"
133 codes[200] = "OK"
134 codes[201] = "Created"
135 codes[202] = "Accepted"
136 codes[203] = "Non-Authoritative Information"
137 codes[204] = "No Content"
138 codes[205] = "Reset Content"
139 codes[206] = "Partial Content"
140 codes[300] = "Multiple Choices"
141 codes[301] = "Moved Permanently"
142 codes[302] = "Found"
143 codes[303] = "See Other"
144 codes[304] = "Not Modified"
145 codes[305] = "Use Proxy"
146 codes[307] = "Temporary Redirect"
147 codes[400] = "Bad Request"
148 codes[401] = "Unauthorized"
149 codes[402] = "Payment Requred"
150 codes[403] = "Forbidden"
151 codes[404] = "Not Found"
152 codes[405] = "Method Not Allowed"
153 codes[406] = "Not Acceptable"
154 codes[407] = "Proxy Authentication Required"
155 codes[408] = "Request Timeout"
156 codes[409] = "Conflict"
157 codes[410] = "Gone"
158 codes[411] = "Length Required"
159 codes[412] = "Precondition Failed"
160 codes[413] = "Request Entity Too Large"
161 codes[414] = "Request-URI Too Long"
162 codes[415] = "Unsupported Media Type"
163 codes[416] = "Requested Range Not Satisfiable"
164 codes[417] = "Expectation Failed"
165 codes[500] = "Internal Server Error"
166 codes[501] = "Not Implemented"
167 codes[502] = "Bad Gateway"
168 codes[503] = "Service Unavailable"
169 codes[504] = "Gateway Timeout"
170 codes[505] = "HTTP Version Not Supported"
171 end
172 end
173
174 # Get the default instance of `HttpStatusCodes`
175 fun http_status_codes: HttpStatusCodes do return once new HttpStatusCodes