|
1 """Generic socket server classes. |
|
2 |
|
3 This module tries to capture the various aspects of defining a server: |
|
4 |
|
5 For socket-based servers: |
|
6 |
|
7 - address family: |
|
8 - AF_INET{,6}: IP (Internet Protocol) sockets (default) |
|
9 - AF_UNIX: Unix domain sockets |
|
10 - others, e.g. AF_DECNET are conceivable (see <socket.h> |
|
11 - socket type: |
|
12 - SOCK_STREAM (reliable stream, e.g. TCP) |
|
13 - SOCK_DGRAM (datagrams, e.g. UDP) |
|
14 |
|
15 For request-based servers (including socket-based): |
|
16 |
|
17 - client address verification before further looking at the request |
|
18 (This is actually a hook for any processing that needs to look |
|
19 at the request before anything else, e.g. logging) |
|
20 - how to handle multiple requests: |
|
21 - synchronous (one request is handled at a time) |
|
22 - forking (each request is handled by a new process) |
|
23 - threading (each request is handled by a new thread) |
|
24 |
|
25 The classes in this module favor the server type that is simplest to |
|
26 write: a synchronous TCP/IP server. This is bad class design, but |
|
27 save some typing. (There's also the issue that a deep class hierarchy |
|
28 slows down method lookups.) |
|
29 |
|
30 There are five classes in an inheritance diagram, four of which represent |
|
31 synchronous servers of four types: |
|
32 |
|
33 +------------+ |
|
34 | BaseServer | |
|
35 +------------+ |
|
36 | |
|
37 v |
|
38 +-----------+ +------------------+ |
|
39 | TCPServer |------->| UnixStreamServer | |
|
40 +-----------+ +------------------+ |
|
41 | |
|
42 v |
|
43 +-----------+ +--------------------+ |
|
44 | UDPServer |------->| UnixDatagramServer | |
|
45 +-----------+ +--------------------+ |
|
46 |
|
47 Note that UnixDatagramServer derives from UDPServer, not from |
|
48 UnixStreamServer -- the only difference between an IP and a Unix |
|
49 stream server is the address family, which is simply repeated in both |
|
50 unix server classes. |
|
51 |
|
52 Forking and threading versions of each type of server can be created |
|
53 using the ForkingMixIn and ThreadingMixIn mix-in classes. For |
|
54 instance, a threading UDP server class is created as follows: |
|
55 |
|
56 class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass |
|
57 |
|
58 The Mix-in class must come first, since it overrides a method defined |
|
59 in UDPServer! Setting the various member variables also changes |
|
60 the behavior of the underlying server mechanism. |
|
61 |
|
62 To implement a service, you must derive a class from |
|
63 BaseRequestHandler and redefine its handle() method. You can then run |
|
64 various versions of the service by combining one of the server classes |
|
65 with your request handler class. |
|
66 |
|
67 The request handler class must be different for datagram or stream |
|
68 services. This can be hidden by using the request handler |
|
69 subclasses StreamRequestHandler or DatagramRequestHandler. |
|
70 |
|
71 Of course, you still have to use your head! |
|
72 |
|
73 For instance, it makes no sense to use a forking server if the service |
|
74 contains state in memory that can be modified by requests (since the |
|
75 modifications in the child process would never reach the initial state |
|
76 kept in the parent process and passed to each child). In this case, |
|
77 you can use a threading server, but you will probably have to use |
|
78 locks to avoid two requests that come in nearly simultaneous to apply |
|
79 conflicting changes to the server state. |
|
80 |
|
81 On the other hand, if you are building e.g. an HTTP server, where all |
|
82 data is stored externally (e.g. in the file system), a synchronous |
|
83 class will essentially render the service "deaf" while one request is |
|
84 being handled -- which may be for a very long time if a client is slow |
|
85 to reqd all the data it has requested. Here a threading or forking |
|
86 server is appropriate. |
|
87 |
|
88 In some cases, it may be appropriate to process part of a request |
|
89 synchronously, but to finish processing in a forked child depending on |
|
90 the request data. This can be implemented by using a synchronous |
|
91 server and doing an explicit fork in the request handler class |
|
92 handle() method. |
|
93 |
|
94 Another approach to handling multiple simultaneous requests in an |
|
95 environment that supports neither threads nor fork (or where these are |
|
96 too expensive or inappropriate for the service) is to maintain an |
|
97 explicit table of partially finished requests and to use select() to |
|
98 decide which request to work on next (or whether to handle a new |
|
99 incoming request). This is particularly important for stream services |
|
100 where each client can potentially be connected for a long time (if |
|
101 threads or subprocesses cannot be used). |
|
102 |
|
103 Future work: |
|
104 - Standard classes for Sun RPC (which uses either UDP or TCP) |
|
105 - Standard mix-in classes to implement various authentication |
|
106 and encryption schemes |
|
107 - Standard framework for select-based multiplexing |
|
108 |
|
109 XXX Open problems: |
|
110 - What to do with out-of-band data? |
|
111 |
|
112 BaseServer: |
|
113 - split generic "request" functionality out into BaseServer class. |
|
114 Copyright (C) 2000 Luke Kenneth Casson Leighton <lkcl@samba.org> |
|
115 |
|
116 example: read entries from a SQL database (requires overriding |
|
117 get_request() to return a table entry from the database). |
|
118 entry is processed by a RequestHandlerClass. |
|
119 |
|
120 """ |
|
121 |
|
122 # Author of the BaseServer patch: Luke Kenneth Casson Leighton |
|
123 |
|
124 # XXX Warning! |
|
125 # There is a test suite for this module, but it cannot be run by the |
|
126 # standard regression test. |
|
127 # To run it manually, run Lib/test/test_socketserver.py. |
|
128 |
|
129 __version__ = "0.4" |
|
130 |
|
131 |
|
132 import socket |
|
133 import select |
|
134 import sys |
|
135 import os |
|
136 try: |
|
137 import threading |
|
138 except ImportError: |
|
139 import dummy_threading as threading |
|
140 |
|
141 __all__ = ["TCPServer","UDPServer","ForkingUDPServer","ForkingTCPServer", |
|
142 "ThreadingUDPServer","ThreadingTCPServer","BaseRequestHandler", |
|
143 "StreamRequestHandler","DatagramRequestHandler", |
|
144 "ThreadingMixIn", "ForkingMixIn"] |
|
145 if hasattr(socket, "AF_UNIX"): |
|
146 __all__.extend(["UnixStreamServer","UnixDatagramServer", |
|
147 "ThreadingUnixStreamServer", |
|
148 "ThreadingUnixDatagramServer"]) |
|
149 |
|
150 class BaseServer: |
|
151 |
|
152 """Base class for server classes. |
|
153 |
|
154 Methods for the caller: |
|
155 |
|
156 - __init__(server_address, RequestHandlerClass) |
|
157 - serve_forever(poll_interval=0.5) |
|
158 - shutdown() |
|
159 - handle_request() # if you do not use serve_forever() |
|
160 - fileno() -> int # for select() |
|
161 |
|
162 Methods that may be overridden: |
|
163 |
|
164 - server_bind() |
|
165 - server_activate() |
|
166 - get_request() -> request, client_address |
|
167 - handle_timeout() |
|
168 - verify_request(request, client_address) |
|
169 - server_close() |
|
170 - process_request(request, client_address) |
|
171 - close_request(request) |
|
172 - handle_error() |
|
173 |
|
174 Methods for derived classes: |
|
175 |
|
176 - finish_request(request, client_address) |
|
177 |
|
178 Class variables that may be overridden by derived classes or |
|
179 instances: |
|
180 |
|
181 - timeout |
|
182 - address_family |
|
183 - socket_type |
|
184 - allow_reuse_address |
|
185 |
|
186 Instance variables: |
|
187 |
|
188 - RequestHandlerClass |
|
189 - socket |
|
190 |
|
191 """ |
|
192 |
|
193 timeout = None |
|
194 |
|
195 def __init__(self, server_address, RequestHandlerClass): |
|
196 """Constructor. May be extended, do not override.""" |
|
197 self.server_address = server_address |
|
198 self.RequestHandlerClass = RequestHandlerClass |
|
199 self.__is_shut_down = threading.Event() |
|
200 self.__serving = False |
|
201 |
|
202 def server_activate(self): |
|
203 """Called by constructor to activate the server. |
|
204 |
|
205 May be overridden. |
|
206 |
|
207 """ |
|
208 pass |
|
209 |
|
210 def serve_forever(self, poll_interval=0.5): |
|
211 """Handle one request at a time until shutdown. |
|
212 |
|
213 Polls for shutdown every poll_interval seconds. Ignores |
|
214 self.timeout. If you need to do periodic tasks, do them in |
|
215 another thread. |
|
216 """ |
|
217 self.__serving = True |
|
218 self.__is_shut_down.clear() |
|
219 while self.__serving: |
|
220 # XXX: Consider using another file descriptor or |
|
221 # connecting to the socket to wake this up instead of |
|
222 # polling. Polling reduces our responsiveness to a |
|
223 # shutdown request and wastes cpu at all other times. |
|
224 r, w, e = select.select([self], [], [], poll_interval) |
|
225 if r: |
|
226 self._handle_request_noblock() |
|
227 self.__is_shut_down.set() |
|
228 |
|
229 def shutdown(self): |
|
230 """Stops the serve_forever loop. |
|
231 |
|
232 Blocks until the loop has finished. This must be called while |
|
233 serve_forever() is running in another thread, or it will |
|
234 deadlock. |
|
235 """ |
|
236 self.__serving = False |
|
237 self.__is_shut_down.wait() |
|
238 |
|
239 # The distinction between handling, getting, processing and |
|
240 # finishing a request is fairly arbitrary. Remember: |
|
241 # |
|
242 # - handle_request() is the top-level call. It calls |
|
243 # select, get_request(), verify_request() and process_request() |
|
244 # - get_request() is different for stream or datagram sockets |
|
245 # - process_request() is the place that may fork a new process |
|
246 # or create a new thread to finish the request |
|
247 # - finish_request() instantiates the request handler class; |
|
248 # this constructor will handle the request all by itself |
|
249 |
|
250 def handle_request(self): |
|
251 """Handle one request, possibly blocking. |
|
252 |
|
253 Respects self.timeout. |
|
254 """ |
|
255 # Support people who used socket.settimeout() to escape |
|
256 # handle_request before self.timeout was available. |
|
257 timeout = self.socket.gettimeout() |
|
258 if timeout is None: |
|
259 timeout = self.timeout |
|
260 elif self.timeout is not None: |
|
261 timeout = min(timeout, self.timeout) |
|
262 fd_sets = select.select([self], [], [], timeout) |
|
263 if not fd_sets[0]: |
|
264 self.handle_timeout() |
|
265 return |
|
266 self._handle_request_noblock() |
|
267 |
|
268 def _handle_request_noblock(self): |
|
269 """Handle one request, without blocking. |
|
270 |
|
271 I assume that select.select has returned that the socket is |
|
272 readable before this function was called, so there should be |
|
273 no risk of blocking in get_request(). |
|
274 """ |
|
275 try: |
|
276 request, client_address = self.get_request() |
|
277 except socket.error: |
|
278 return |
|
279 if self.verify_request(request, client_address): |
|
280 try: |
|
281 self.process_request(request, client_address) |
|
282 except: |
|
283 self.handle_error(request, client_address) |
|
284 self.close_request(request) |
|
285 |
|
286 def handle_timeout(self): |
|
287 """Called if no new request arrives within self.timeout. |
|
288 |
|
289 Overridden by ForkingMixIn. |
|
290 """ |
|
291 pass |
|
292 |
|
293 def verify_request(self, request, client_address): |
|
294 """Verify the request. May be overridden. |
|
295 |
|
296 Return True if we should proceed with this request. |
|
297 |
|
298 """ |
|
299 return True |
|
300 |
|
301 def process_request(self, request, client_address): |
|
302 """Call finish_request. |
|
303 |
|
304 Overridden by ForkingMixIn and ThreadingMixIn. |
|
305 |
|
306 """ |
|
307 self.finish_request(request, client_address) |
|
308 self.close_request(request) |
|
309 |
|
310 def server_close(self): |
|
311 """Called to clean-up the server. |
|
312 |
|
313 May be overridden. |
|
314 |
|
315 """ |
|
316 pass |
|
317 |
|
318 def finish_request(self, request, client_address): |
|
319 """Finish one request by instantiating RequestHandlerClass.""" |
|
320 self.RequestHandlerClass(request, client_address, self) |
|
321 |
|
322 def close_request(self, request): |
|
323 """Called to clean up an individual request.""" |
|
324 pass |
|
325 |
|
326 def handle_error(self, request, client_address): |
|
327 """Handle an error gracefully. May be overridden. |
|
328 |
|
329 The default is to print a traceback and continue. |
|
330 |
|
331 """ |
|
332 print '-'*40 |
|
333 print 'Exception happened during processing of request from', |
|
334 print client_address |
|
335 import traceback |
|
336 traceback.print_exc() # XXX But this goes to stderr! |
|
337 print '-'*40 |
|
338 |
|
339 |
|
340 class TCPServer(BaseServer): |
|
341 |
|
342 """Base class for various socket-based server classes. |
|
343 |
|
344 Defaults to synchronous IP stream (i.e., TCP). |
|
345 |
|
346 Methods for the caller: |
|
347 |
|
348 - __init__(server_address, RequestHandlerClass, bind_and_activate=True) |
|
349 - serve_forever(poll_interval=0.5) |
|
350 - shutdown() |
|
351 - handle_request() # if you don't use serve_forever() |
|
352 - fileno() -> int # for select() |
|
353 |
|
354 Methods that may be overridden: |
|
355 |
|
356 - server_bind() |
|
357 - server_activate() |
|
358 - get_request() -> request, client_address |
|
359 - handle_timeout() |
|
360 - verify_request(request, client_address) |
|
361 - process_request(request, client_address) |
|
362 - close_request(request) |
|
363 - handle_error() |
|
364 |
|
365 Methods for derived classes: |
|
366 |
|
367 - finish_request(request, client_address) |
|
368 |
|
369 Class variables that may be overridden by derived classes or |
|
370 instances: |
|
371 |
|
372 - timeout |
|
373 - address_family |
|
374 - socket_type |
|
375 - request_queue_size (only for stream sockets) |
|
376 - allow_reuse_address |
|
377 |
|
378 Instance variables: |
|
379 |
|
380 - server_address |
|
381 - RequestHandlerClass |
|
382 - socket |
|
383 |
|
384 """ |
|
385 |
|
386 address_family = socket.AF_INET |
|
387 |
|
388 socket_type = socket.SOCK_STREAM |
|
389 |
|
390 request_queue_size = 5 |
|
391 |
|
392 allow_reuse_address = False |
|
393 |
|
394 def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True): |
|
395 """Constructor. May be extended, do not override.""" |
|
396 BaseServer.__init__(self, server_address, RequestHandlerClass) |
|
397 self.socket = socket.socket(self.address_family, |
|
398 self.socket_type) |
|
399 if bind_and_activate: |
|
400 self.server_bind() |
|
401 self.server_activate() |
|
402 |
|
403 def server_bind(self): |
|
404 """Called by constructor to bind the socket. |
|
405 |
|
406 May be overridden. |
|
407 |
|
408 """ |
|
409 if self.allow_reuse_address: |
|
410 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) |
|
411 self.socket.bind(self.server_address) |
|
412 self.server_address = self.socket.getsockname() |
|
413 |
|
414 def server_activate(self): |
|
415 """Called by constructor to activate the server. |
|
416 |
|
417 May be overridden. |
|
418 |
|
419 """ |
|
420 self.socket.listen(self.request_queue_size) |
|
421 |
|
422 def server_close(self): |
|
423 """Called to clean-up the server. |
|
424 |
|
425 May be overridden. |
|
426 |
|
427 """ |
|
428 self.socket.close() |
|
429 |
|
430 def fileno(self): |
|
431 """Return socket file number. |
|
432 |
|
433 Interface required by select(). |
|
434 |
|
435 """ |
|
436 return self.socket.fileno() |
|
437 |
|
438 def get_request(self): |
|
439 """Get the request and client address from the socket. |
|
440 |
|
441 May be overridden. |
|
442 |
|
443 """ |
|
444 return self.socket.accept() |
|
445 |
|
446 def close_request(self, request): |
|
447 """Called to clean up an individual request.""" |
|
448 request.close() |
|
449 |
|
450 |
|
451 class UDPServer(TCPServer): |
|
452 |
|
453 """UDP server class.""" |
|
454 |
|
455 allow_reuse_address = False |
|
456 |
|
457 socket_type = socket.SOCK_DGRAM |
|
458 |
|
459 max_packet_size = 8192 |
|
460 |
|
461 def get_request(self): |
|
462 data, client_addr = self.socket.recvfrom(self.max_packet_size) |
|
463 return (data, self.socket), client_addr |
|
464 |
|
465 def server_activate(self): |
|
466 # No need to call listen() for UDP. |
|
467 pass |
|
468 |
|
469 def close_request(self, request): |
|
470 # No need to close anything. |
|
471 pass |
|
472 |
|
473 class ForkingMixIn: |
|
474 |
|
475 """Mix-in class to handle each request in a new process.""" |
|
476 |
|
477 timeout = 300 |
|
478 active_children = None |
|
479 max_children = 40 |
|
480 |
|
481 def collect_children(self): |
|
482 """Internal routine to wait for children that have exited.""" |
|
483 if self.active_children is None: return |
|
484 while len(self.active_children) >= self.max_children: |
|
485 # XXX: This will wait for any child process, not just ones |
|
486 # spawned by this library. This could confuse other |
|
487 # libraries that expect to be able to wait for their own |
|
488 # children. |
|
489 try: |
|
490 pid, status = os.waitpid(0, options=0) |
|
491 except os.error: |
|
492 pid = None |
|
493 if pid not in self.active_children: continue |
|
494 self.active_children.remove(pid) |
|
495 |
|
496 # XXX: This loop runs more system calls than it ought |
|
497 # to. There should be a way to put the active_children into a |
|
498 # process group and then use os.waitpid(-pgid) to wait for any |
|
499 # of that set, but I couldn't find a way to allocate pgids |
|
500 # that couldn't collide. |
|
501 for child in self.active_children: |
|
502 try: |
|
503 pid, status = os.waitpid(child, os.WNOHANG) |
|
504 except os.error: |
|
505 pid = None |
|
506 if not pid: continue |
|
507 try: |
|
508 self.active_children.remove(pid) |
|
509 except ValueError, e: |
|
510 raise ValueError('%s. x=%d and list=%r' % (e.message, pid, |
|
511 self.active_children)) |
|
512 |
|
513 def handle_timeout(self): |
|
514 """Wait for zombies after self.timeout seconds of inactivity. |
|
515 |
|
516 May be extended, do not override. |
|
517 """ |
|
518 self.collect_children() |
|
519 |
|
520 def process_request(self, request, client_address): |
|
521 """Fork a new subprocess to process the request.""" |
|
522 self.collect_children() |
|
523 pid = os.fork() |
|
524 if pid: |
|
525 # Parent process |
|
526 if self.active_children is None: |
|
527 self.active_children = [] |
|
528 self.active_children.append(pid) |
|
529 self.close_request(request) |
|
530 return |
|
531 else: |
|
532 # Child process. |
|
533 # This must never return, hence os._exit()! |
|
534 try: |
|
535 self.finish_request(request, client_address) |
|
536 os._exit(0) |
|
537 except: |
|
538 try: |
|
539 self.handle_error(request, client_address) |
|
540 finally: |
|
541 os._exit(1) |
|
542 |
|
543 |
|
544 class ThreadingMixIn: |
|
545 """Mix-in class to handle each request in a new thread.""" |
|
546 |
|
547 # Decides how threads will act upon termination of the |
|
548 # main process |
|
549 daemon_threads = False |
|
550 |
|
551 def process_request_thread(self, request, client_address): |
|
552 """Same as in BaseServer but as a thread. |
|
553 |
|
554 In addition, exception handling is done here. |
|
555 |
|
556 """ |
|
557 try: |
|
558 self.finish_request(request, client_address) |
|
559 self.close_request(request) |
|
560 except: |
|
561 self.handle_error(request, client_address) |
|
562 self.close_request(request) |
|
563 |
|
564 def process_request(self, request, client_address): |
|
565 """Start a new thread to process the request.""" |
|
566 t = threading.Thread(target = self.process_request_thread, |
|
567 args = (request, client_address)) |
|
568 if self.daemon_threads: |
|
569 t.setDaemon (1) |
|
570 t.start() |
|
571 |
|
572 |
|
573 class ForkingUDPServer(ForkingMixIn, UDPServer): pass |
|
574 class ForkingTCPServer(ForkingMixIn, TCPServer): pass |
|
575 |
|
576 class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass |
|
577 class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass |
|
578 |
|
579 if hasattr(socket, 'AF_UNIX'): |
|
580 |
|
581 class UnixStreamServer(TCPServer): |
|
582 address_family = socket.AF_UNIX |
|
583 |
|
584 class UnixDatagramServer(UDPServer): |
|
585 address_family = socket.AF_UNIX |
|
586 |
|
587 class ThreadingUnixStreamServer(ThreadingMixIn, UnixStreamServer): pass |
|
588 |
|
589 class ThreadingUnixDatagramServer(ThreadingMixIn, UnixDatagramServer): pass |
|
590 |
|
591 class BaseRequestHandler: |
|
592 |
|
593 """Base class for request handler classes. |
|
594 |
|
595 This class is instantiated for each request to be handled. The |
|
596 constructor sets the instance variables request, client_address |
|
597 and server, and then calls the handle() method. To implement a |
|
598 specific service, all you need to do is to derive a class which |
|
599 defines a handle() method. |
|
600 |
|
601 The handle() method can find the request as self.request, the |
|
602 client address as self.client_address, and the server (in case it |
|
603 needs access to per-server information) as self.server. Since a |
|
604 separate instance is created for each request, the handle() method |
|
605 can define arbitrary other instance variariables. |
|
606 |
|
607 """ |
|
608 |
|
609 def __init__(self, request, client_address, server): |
|
610 self.request = request |
|
611 self.client_address = client_address |
|
612 self.server = server |
|
613 try: |
|
614 self.setup() |
|
615 self.handle() |
|
616 self.finish() |
|
617 finally: |
|
618 sys.exc_traceback = None # Help garbage collection |
|
619 |
|
620 def setup(self): |
|
621 pass |
|
622 |
|
623 def handle(self): |
|
624 pass |
|
625 |
|
626 def finish(self): |
|
627 pass |
|
628 |
|
629 |
|
630 # The following two classes make it possible to use the same service |
|
631 # class for stream or datagram servers. |
|
632 # Each class sets up these instance variables: |
|
633 # - rfile: a file object from which receives the request is read |
|
634 # - wfile: a file object to which the reply is written |
|
635 # When the handle() method returns, wfile is flushed properly |
|
636 |
|
637 |
|
638 class StreamRequestHandler(BaseRequestHandler): |
|
639 |
|
640 """Define self.rfile and self.wfile for stream sockets.""" |
|
641 |
|
642 # Default buffer sizes for rfile, wfile. |
|
643 # We default rfile to buffered because otherwise it could be |
|
644 # really slow for large data (a getc() call per byte); we make |
|
645 # wfile unbuffered because (a) often after a write() we want to |
|
646 # read and we need to flush the line; (b) big writes to unbuffered |
|
647 # files are typically optimized by stdio even when big reads |
|
648 # aren't. |
|
649 rbufsize = -1 |
|
650 wbufsize = 0 |
|
651 |
|
652 def setup(self): |
|
653 self.connection = self.request |
|
654 self.rfile = self.connection.makefile('rb', self.rbufsize) |
|
655 self.wfile = self.connection.makefile('wb', self.wbufsize) |
|
656 |
|
657 def finish(self): |
|
658 if not self.wfile.closed: |
|
659 self.wfile.flush() |
|
660 self.wfile.close() |
|
661 self.rfile.close() |
|
662 |
|
663 |
|
664 class DatagramRequestHandler(BaseRequestHandler): |
|
665 |
|
666 # XXX Regrettably, I cannot get this working on Linux; |
|
667 # s.recvfrom() doesn't return a meaningful client address. |
|
668 |
|
669 """Define self.rfile and self.wfile for datagram sockets.""" |
|
670 |
|
671 def setup(self): |
|
672 try: |
|
673 from cStringIO import StringIO |
|
674 except ImportError: |
|
675 from StringIO import StringIO |
|
676 self.packet, self.socket = self.request |
|
677 self.rfile = StringIO(self.packet) |
|
678 self.wfile = StringIO() |
|
679 |
|
680 def finish(self): |
|
681 self.socket.sendto(self.wfile.getvalue(), self.client_address) |