Statistics
| Branch: | Tag: | Revision:

root / lib / http.py @ a0638838

History | View | Annotate | Download (6.9 kB)

1
#
2
#
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.
7
#
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.
12
#
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
16
# 02110-1301, USA.
17

    
18
"""HTTP server module.
19

20
"""
21

    
22
import socket
23
import BaseHTTPServer
24
import OpenSSL
25
import time
26
import logging
27

    
28
from ganeti import errors
29
from ganeti import logger
30
from ganeti import serializer
31

    
32

    
33
class HTTPException(Exception):
34
  code = None
35
  message = None
36

    
37
  def __init__(self, message=None):
38
    if message is not None:
39
      self.message = message
40

    
41

    
42
class HTTPBadRequest(HTTPException):
43
  code = 400
44

    
45

    
46
class HTTPForbidden(HTTPException):
47
  code = 403
48

    
49

    
50
class HTTPNotFound(HTTPException):
51
  code = 404
52

    
53

    
54
class HTTPGone(HTTPException):
55
  code = 410
56

    
57

    
58
class HTTPLengthRequired(HTTPException):
59
  code = 411
60

    
61

    
62
class HTTPInternalError(HTTPException):
63
  code = 500
64

    
65

    
66
class HTTPNotImplemented(HTTPException):
67
  code = 501
68

    
69

    
70
class HTTPServiceUnavailable(HTTPException):
71
  code = 503
72

    
73

    
74
class ApacheLogfile:
75
  """Utility class to write HTTP server log files.
76

77
  The written format is the "Common Log Format" as defined by Apache:
78
  http://httpd.apache.org/docs/2.2/mod/mod_log_config.html#examples
79

80
  """
81
  MONTHNAME = [None,
82
               'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
83
               'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
84

    
85
  def __init__(self, fd):
86
    """Constructor for ApacheLogfile class.
87

88
    Args:
89
    - fd: Open file object
90

91
    """
92
    self._fd = fd
93

    
94
  def LogRequest(self, request, format, *args):
95
    self._fd.write("%s %s %s [%s] %s\n" % (
96
      # Remote host address
97
      request.address_string(),
98

    
99
      # RFC1413 identity (identd)
100
      "-",
101

    
102
      # Remote user
103
      "-",
104

    
105
      # Request time
106
      self._FormatCurrentTime(),
107

    
108
      # Message
109
      format % args,
110
      ))
111
    self._fd.flush()
112

    
113
  def _FormatCurrentTime(self):
114
    """Formats current time in Common Log Format.
115

116
    """
117
    return self._FormatLogTime(time.time())
118

    
119
  def _FormatLogTime(self, seconds):
120
    """Formats time for Common Log Format.
121

122
    All timestamps are logged in the UTC timezone.
123

124
    Args:
125
    - seconds: Time in seconds since the epoch
126

127
    """
128
    (_, month, _, _, _, _, _, _, _) = tm = time.gmtime(seconds)
129
    format = "%d/" + self.MONTHNAME[month] + "/%Y:%H:%M:%S +0000"
130
    return time.strftime(format, tm)
131

    
132

    
133
class HTTPServer(BaseHTTPServer.HTTPServer, object):
134
  """Class to provide an HTTP/HTTPS server.
135

136
  """
137
  allow_reuse_address = True
138

    
139
  def __init__(self, server_address, HandlerClass, httplog=None,
140
               enable_ssl=False, ssl_key=None, ssl_cert=None):
141
    """Server constructor.
142

143
    Args:
144
      server_address: a touple containing:
145
        ip: a string with IP address, localhost if empty string
146
        port: port number, integer
147
      HandlerClass: HTTPRequestHandler object
148
      httplog: Access log object
149
      enable_ssl: Whether to enable SSL
150
      ssl_key: SSL key file
151
      ssl_cert: SSL certificate key
152

153
    """
154
    BaseHTTPServer.HTTPServer.__init__(self, server_address, HandlerClass)
155

    
156
    self.httplog = httplog
157

    
158
    if enable_ssl:
159
      # Set up SSL
160
      context = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
161
      context.use_privatekey_file(ssl_key)
162
      context.use_certificate_file(ssl_cert)
163
      self.socket = OpenSSL.SSL.Connection(context,
164
                                           socket.socket(self.address_family,
165
                                           self.socket_type))
166
    else:
167
      self.socket = socket.socket(self.address_family, self.socket_type)
168

    
169
    self.server_bind()
170
    self.server_activate()
171

    
172

    
173
class HTTPJsonConverter:
174
  CONTENT_TYPE = "application/json"
175

    
176
  def Encode(self, data):
177
    return serializer.DumpJson(data)
178

    
179
  def Decode(self, data):
180
    return serializer.LoadJson(data)
181

    
182

    
183
class HTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler, object):
184
  """Request handler class.
185

186
  """
187
  def setup(self):
188
    """Setup secure read and write file objects.
189

190
    """
191
    self.connection = self.request
192
    self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
193
    self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
194

    
195
  def handle_one_request(self):
196
    """Parses a request and calls the handler function.
197

198
    """
199
    self.raw_requestline = None
200
    try:
201
      self.raw_requestline = self.rfile.readline()
202
    except OpenSSL.SSL.Error, ex:
203
      logger.Error("Error in SSL: %s" % str(ex))
204
    if not self.raw_requestline:
205
      self.close_connection = 1
206
      return
207
    if not self.parse_request(): # An error code has been sent, just exit
208
      return
209
    logging.debug("HTTP request: %s", self.raw_requestline.rstrip("\r\n"))
210

    
211
    try:
212
      self._ReadPostData()
213

    
214
      result = self.HandleRequest()
215

    
216
      # TODO: Content-type
217
      encoder = HTTPJsonConverter()
218
      encoded_result = encoder.Encode(result)
219

    
220
      self.send_response(200)
221
      self.send_header("Content-Type", encoder.CONTENT_TYPE)
222
      self.send_header("Content-Length", str(len(encoded_result)))
223
      self.end_headers()
224

    
225
      self.wfile.write(encoded_result)
226

    
227
    except HTTPException, err:
228
      self.send_error(err.code, message=err.message)
229

    
230
    except Exception, err:
231
      self.send_error(HTTPInternalError.code, message=str(err))
232

    
233
    except:
234
      self.send_error(HTTPInternalError.code, message="Unknown error")
235

    
236
  def _ReadPostData(self):
237
    if self.command.upper() not in ("POST", "PUT"):
238
      self.post_data = None
239
      return
240

    
241
    # TODO: Decide what to do when Content-Length header was not sent
242
    try:
243
      content_length = int(self.headers.get('Content-Length', 0))
244
    except ValueError:
245
      raise HTTPBadRequest("No Content-Length header or invalid format")
246

    
247
    try:
248
      data = self.rfile.read(content_length)
249
    except socket.error, err:
250
      logger.Error("Socket error while reading: %s" % str(err))
251
      return
252

    
253
    # TODO: Content-type, error handling
254
    self.post_data = HTTPJsonConverter().Decode(data)
255

    
256
    logging.debug("HTTP POST data: %s", self.post_data)
257

    
258
  def HandleRequest(self):
259
    """Handles a request.
260

261
    """
262
    raise NotImplementedError()
263

    
264
  def log_message(self, format, *args):
265
    """Log an arbitrary message.
266

267
    This is used by all other logging functions.
268

269
    The first argument, FORMAT, is a format string for the
270
    message to be logged.  If the format string contains
271
    any % escapes requiring parameters, they should be
272
    specified as subsequent arguments (it's just like
273
    printf!).
274

275
    """
276
    logging.debug("Handled request: %s", format % args)
277
    if self.server.httplog:
278
      self.server.httplog.LogRequest(self, format, *args)