3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful, but
9 # WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 # General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 """HTTP server module.
28 from ganeti import logger
29 from ganeti import serializer
32 class HTTPException(Exception):
36 def __init__(self, message=None):
37 if message is not None:
38 self.message = message
41 class HTTPBadRequest(HTTPException):
45 class HTTPForbidden(HTTPException):
49 class HTTPNotFound(HTTPException):
53 class HTTPGone(HTTPException):
57 class HTTPLengthRequired(HTTPException):
61 class HTTPInternalError(HTTPException):
65 class HTTPNotImplemented(HTTPException):
69 class HTTPServiceUnavailable(HTTPException):
74 """Utility class to write HTTP server log files.
76 The written format is the "Common Log Format" as defined by Apache:
77 http://httpd.apache.org/docs/2.2/mod/mod_log_config.html#examples
81 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
82 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
84 def __init__(self, fd):
85 """Constructor for ApacheLogfile class.
88 - fd: Open file object
93 def LogRequest(self, request, format, *args):
94 self._fd.write("%s %s %s [%s] %s\n" % (
96 request.address_string(),
98 # RFC1413 identity (identd)
105 self._FormatCurrentTime(),
112 def _FormatCurrentTime(self):
113 """Formats current time in Common Log Format.
116 return self._FormatLogTime(time.time())
118 def _FormatLogTime(self, seconds):
119 """Formats time for Common Log Format.
121 All timestamps are logged in the UTC timezone.
124 - seconds: Time in seconds since the epoch
127 (_, month, _, _, _, _, _, _, _) = tm = time.gmtime(seconds)
128 format = "%d/" + self.MONTHNAME[month] + "/%Y:%H:%M:%S +0000"
129 return time.strftime(format, tm)
132 class HTTPServer(BaseHTTPServer.HTTPServer, object):
133 """Class to provide an HTTP/HTTPS server.
136 allow_reuse_address = True
138 def __init__(self, server_address, HandlerClass, httplog=None,
139 enable_ssl=False, ssl_key=None, ssl_cert=None):
140 """Server constructor.
143 server_address: a touple containing:
144 ip: a string with IP address, localhost if empty string
145 port: port number, integer
146 HandlerClass: HTTPRequestHandler object
147 httplog: Access log object
148 enable_ssl: Whether to enable SSL
149 ssl_key: SSL key file
150 ssl_cert: SSL certificate key
153 BaseHTTPServer.HTTPServer.__init__(self, server_address, HandlerClass)
155 self.httplog = httplog
159 context = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
160 context.use_privatekey_file(ssl_key)
161 context.use_certificate_file(ssl_cert)
162 self.socket = OpenSSL.SSL.Connection(context,
163 socket.socket(self.address_family,
166 self.socket = socket.socket(self.address_family, self.socket_type)
169 self.server_activate()
172 class HTTPJsonConverter:
173 CONTENT_TYPE = "application/json"
175 def Encode(self, data):
176 return serializer.DumpJson(data)
178 def Decode(self, data):
179 return serializer.LoadJson(data)
182 class HTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler, object):
183 """Request handler class.
187 """Setup secure read and write file objects.
190 self.connection = self.request
191 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
192 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
194 def handle_one_request(self):
195 """Parses a request and calls the handler function.
198 self.raw_requestline = None
200 self.raw_requestline = self.rfile.readline()
201 except OpenSSL.SSL.Error, ex:
202 logger.Error("Error in SSL: %s" % str(ex))
203 if not self.raw_requestline:
204 self.close_connection = 1
206 if not self.parse_request(): # An error code has been sent, just exit
208 logging.debug("HTTP request: %s", self.raw_requestline.rstrip("\r\n"))
213 result = self.HandleRequest()
216 encoder = HTTPJsonConverter()
217 encoded_result = encoder.Encode(result)
219 self.send_response(200)
220 self.send_header("Content-Type", encoder.CONTENT_TYPE)
221 self.send_header("Content-Length", str(len(encoded_result)))
224 self.wfile.write(encoded_result)
226 except HTTPException, err:
227 self.send_error(err.code, message=err.message)
229 except Exception, err:
230 self.send_error(HTTPInternalError.code, message=str(err))
233 self.send_error(HTTPInternalError.code, message="Unknown error")
235 def _ReadPostData(self):
236 if self.command.upper() not in ("POST", "PUT"):
237 self.post_data = None
240 # TODO: Decide what to do when Content-Length header was not sent
242 content_length = int(self.headers.get('Content-Length', 0))
244 raise HTTPBadRequest("No Content-Length header or invalid format")
247 data = self.rfile.read(content_length)
248 except socket.error, err:
249 logger.Error("Socket error while reading: %s" % str(err))
252 # TODO: Content-type, error handling
253 self.post_data = HTTPJsonConverter().Decode(data)
255 logging.debug("HTTP POST data: %s", self.post_data)
257 def HandleRequest(self):
258 """Handles a request.
261 raise NotImplementedError()
263 def log_message(self, format, *args):
264 """Log an arbitrary message.
266 This is used by all other logging functions.
268 The first argument, FORMAT, is a format string for the
269 message to be logged. If the format string contains
270 any % escapes requiring parameters, they should be
271 specified as subsequent arguments (it's just like
275 logging.debug("Handled request: %s", format % args)
276 if self.server.httplog:
277 self.server.httplog.LogRequest(self, format, *args)