Revision a43f68dc

b/Makefile.am
67 67
	lib/config.py \
68 68
	lib/constants.py \
69 69
	lib/errors.py \
70
	lib/http.py \
70 71
	lib/jqueue.py \
71 72
	lib/locking.py \
72 73
	lib/logger.py \
b/lib/http.py
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

  
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)

Also available in: Unified diff