Statistics
| Branch: | Tag: | Revision:

root / lib / http.py @ a1578d63

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 logger
29
from ganeti import serializer
30

    
31

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

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

    
40

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

    
44

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

    
48

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

    
52

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

    
56

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

    
60

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

    
64

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

    
68

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

    
72

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

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
78

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

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

87
    Args:
88
    - fd: Open file object
89

90
    """
91
    self._fd = fd
92

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

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

    
101
      # Remote user
102
      "-",
103

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

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

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

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

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

121
    All timestamps are logged in the UTC timezone.
122

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

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

    
131

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

135
  """
136
  allow_reuse_address = True
137

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

142
    Args:
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
151

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

    
155
    self.httplog = httplog
156

    
157
    if enable_ssl:
158
      # Set up SSL
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,
164
                                           self.socket_type))
165
    else:
166
      self.socket = socket.socket(self.address_family, self.socket_type)
167

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

    
171

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

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

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

    
181

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

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

189
    """
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)
193

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

197
    """
198
    self.raw_requestline = None
199
    try:
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
205
      return
206
    if not self.parse_request(): # An error code has been sent, just exit
207
      return
208
    logging.debug("HTTP request: %s", self.raw_requestline.rstrip("\r\n"))
209

    
210
    try:
211
      self._ReadPostData()
212

    
213
      result = self.HandleRequest()
214

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

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

    
224
      self.wfile.write(encoded_result)
225

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

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

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

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

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

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

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

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

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

260
    """
261
    raise NotImplementedError()
262

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

266
    This is used by all other logging functions.
267

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
272
    printf!).
273

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