default_handler.handle_request()   F
last analyzed

Complexity

Conditions 25

Size

Total Lines 93
Code Lines 70

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 25
eloc 70
nop 2
dl 0
loc 93
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like medusa.default_handler.default_handler.handle_request() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# -*- Mode: Python; tab-width: 4 -*-
2
#
3
#    Author: Sam Rushing <[email protected]>
4
#    Copyright 1997 by Sam Rushing
5
#                         All Rights Reserved.
6
#
7
8
# standard python modules
9
import os
10
import re
11
import posixpath
12
import stat
13
import string
14
import time
15
16
# medusa modules
17
import http_date
18
import http_server
19
import mime_type_table
20
import status_handler
21
import producers
22
23
unquote = http_server.unquote
24
25
# This is the 'default' handler.  it implements the base set of
26
# features expected of a simple file-delivering HTTP server.  file
27
# services are provided through a 'filesystem' object, the very same
28
# one used by the FTP server.
29
#
30
# You can replace or modify this handler if you want a non-standard
31
# HTTP server.  You can also derive your own handler classes from
32
# it.
33
#
34
# support for handling POST requests is available in the derived
35
# class <default_with_post_handler>, defined below.
36
#
37
38
from counter import counter
39
40
class default_handler:
41
42
    valid_commands = ['get', 'head']
43
44
    IDENT = 'Default HTTP Request Handler'
45
46
    # Pathnames that are tried when a URI resolves to a directory name
47
    directory_defaults = [
48
        'index.html',
49
        'default.html'
50
        ]
51
52
    default_file_producer = producers.file_producer
53
54
    def __init__ (self, filesystem):
55
        self.filesystem = filesystem
56
        # count total hits
57
        self.hit_counter = counter()
58
        # count file deliveries
59
        self.file_counter = counter()
60
        # count cache hits
61
        self.cache_counter = counter()
62
63
    hit_counter = 0
64
65
    def __repr__ (self):
66
        return '<%s (%s hits) at %x>' % (
67
            self.IDENT,
68
            self.hit_counter,
69
            id (self)
70
            )
71
72
    # always match, since this is a default
73
    def match (self, request):
74
        return 1
75
76
    # handle a file request, with caching.
77
78
    def handle_request (self, request):
79
80
        if request.command not in self.valid_commands:
81
            request.error (400) # bad request
82
            return
83
84
        self.hit_counter.increment()
85
86
        path, params, query, fragment = request.split_uri()
87
88
        if '%' in path:
89
            path = unquote (path)
90
91
        # strip off all leading slashes
92
        while path and path[0] == '/':
93
            path = path[1:]
94
95
        if self.filesystem.isdir (path):
96
            if path and path[-1] != '/':
97
                request['Location'] = 'http://%s/%s/' % (
98
                    request.channel.server.server_name,
99
                    path
100
                    )
101
                request.error (301)
102
                return
103
104
            # we could also generate a directory listing here,
105
            # may want to move this into another method for that
106
            # purpose
107
            found = 0
108
            if path and path[-1] != '/':
109
                path = path + '/'
110
            for default in self.directory_defaults:
111
                p = path + default
112
                if self.filesystem.isfile (p):
113
                    path = p
114
                    found = 1
115
                    break
116
            if not found:
117
                request.error (404) # Not Found
118
                return
119
120
        elif not self.filesystem.isfile (path):
121
            request.error (404) # Not Found
122
            return
123
124
        file_length = self.filesystem.stat (path)[stat.ST_SIZE]
125
126
        ims = get_header_match (IF_MODIFIED_SINCE, request.header)
127
128
        length_match = 1
129
        if ims:
130
            length = ims.group (4)
131
            if length:
132
                try:
133
                    length = string.atoi (length)
134
                    if length != file_length:
135
                        length_match = 0
136
                except:
137
                    pass
138
139
        ims_date = 0
140
141
        if ims:
142
            ims_date = http_date.parse_http_date (ims.group (1))
143
144
        try:
145
            mtime = self.filesystem.stat (path)[stat.ST_MTIME]
146
        except:
147
            request.error (404)
148
            return
149
150
        if length_match and ims_date:
151
            if mtime <= ims_date:
152
                request.reply_code = 304
153
                request.done()
154
                self.cache_counter.increment()
155
                return
156
        try:
157
            file = self.filesystem.open (path, 'rb')
158
        except IOError:
159
            request.error (404)
160
            return
161
162
        request['Last-Modified'] = http_date.build_http_date (mtime)
163
        request['Content-Length'] = file_length
164
        self.set_content_type (path, request)
165
166
        if request.command == 'get':
167
            request.push (self.default_file_producer (file))
168
169
        self.file_counter.increment()
170
        request.done()
171
172
    def set_content_type (self, path, request):
173
        ext = string.lower (get_extension (path))
174
        if mime_type_table.content_type_map.has_key (ext):
175
            request['Content-Type'] = mime_type_table.content_type_map[ext]
176
        else:
177
            # TODO: test a chunk off the front of the file for 8-bit
178
            # characters, and use application/octet-stream instead.
179
            request['Content-Type'] = 'text/plain'
180
181
    def status (self):
182
        return producers.simple_producer (
183
            '<li>%s' % status_handler.html_repr (self)
184
            + '<ul>'
185
            + '  <li><b>Total Hits:</b> %s'            % self.hit_counter
186
            + '  <li><b>Files Delivered:</b> %s'    % self.file_counter
187
            + '  <li><b>Cache Hits:</b> %s'            % self.cache_counter
188
            + '</ul>'
189
            )
190
191
# HTTP/1.0 doesn't say anything about the "; length=nnnn" addition
192
# to this header.  I suppose it's purpose is to avoid the overhead
193
# of parsing dates...
194
IF_MODIFIED_SINCE = re.compile (
195
    'If-Modified-Since: ([^;]+)((; length=([0-9]+)$)|$)',
196
    re.IGNORECASE
197
    )
198
199
USER_AGENT = re.compile ('User-Agent: (.*)', re.IGNORECASE)
200
201
CONTENT_TYPE = re.compile (
202
    r'Content-Type: ([^;]+)((; boundary=([A-Za-z0-9\'\(\)+_,./:=?-]+)$)|$)',
203
    re.IGNORECASE
204
    )
205
206
get_header = http_server.get_header
207
get_header_match = http_server.get_header_match
208
209
def get_extension (path):
210
    dirsep = string.rfind (path, '/')
211
    dotsep = string.rfind (path, '.')
212
    if dotsep > dirsep:
213
        return path[dotsep+1:]
214
    else:
215
        return ''
216