Package restkit :: Module parser
[hide private]
[frames] | no frames]

Source Code for Module restkit.parser

  1  # -*- coding: utf-8 - 
  2  # 
  3  # This file is part of restkit released under the MIT license.  
  4  # See the NOTICE for more information. 
  5   
  6  from StringIO import StringIO 
  7  import urlparse 
  8   
  9  from restkit.errors import BadStatusLine, ParserError 
10 11 -class Parser(object):
12 """ HTTP Parser compatible 1.0 & 1.1 13 This parser can parse HTTP requests and response. 14 """ 15
16 - def __init__(self, ptype='response', should_close=False):
17 self.status_line = "" 18 self.status_int = None 19 self.reason = "" 20 self.status = "" 21 self.headers = [] 22 self.headers_dict = {} 23 self.raw_version = "HTTP/1.0" 24 self.raw_path = "" 25 self.version = (1,0) 26 self.method = "" 27 self.path = "" 28 self.query_string = "" 29 self.fragment = "" 30 self._content_len = None 31 self.start_offset = 0 32 self.chunk_size = 0 33 self._chunk_eof = False 34 self.type = ptype 35 self._should_close = should_close
36 37 @classmethod
38 - def parse_response(cls, should_close=False):
39 """ Return parser object for response""" 40 return cls(should_close=should_close)
41 42 @classmethod
43 - def parse_request(cls):
44 """ return parser object for requests """ 45 return cls(ptype='request')
46
47 - def filter_headers(self, headers, buf):
48 """ take a string as buffer and an header dict 49 (empty or not). It return new position or -1 50 if parsing isn't done. headers dict is updated 51 with new headers. 52 """ 53 line = buf.getvalue() 54 i = line.find("\r\n\r\n") 55 if i != -1: 56 r = line[:i] 57 pos = i+4 58 buf2 = StringIO() 59 buf2.write(line[pos:]) 60 return self.finalize_headers(headers, r, buf2) 61 return False
62
63 - def finalize_headers(self, headers, headers_str, buf2):
64 """ parse the headers """ 65 lines = headers_str.split("\r\n") 66 67 # parse first line of headers 68 self._first_line(lines.pop(0)) 69 70 # parse headers. We silently ignore 71 # bad headers' lines 72 73 _headers = {} 74 hname = "" 75 for line in lines: 76 if line.startswith('\t') or line.startswith(' '): 77 headers[hname] += line.strip() 78 else: 79 try: 80 hname =self._parse_headerl(_headers, line) 81 except ValueError: 82 # bad headers 83 pass 84 self.headers_dict = _headers 85 headers.extend(list(_headers.items())) 86 self.headers = headers 87 self._content_len = int(_headers.get('Content-Length',0)) 88 89 if self.type == 'request': 90 (_, _, self.path, self.query_string, self.fragment) = \ 91 urlparse.urlsplit(self.raw_path) 92 93 return buf2
94
95 - def _parse_version(self, version):
96 self.raw_version = version.strip() 97 try: 98 major, minor = self.raw_version.split("HTTP/")[1].split(".") 99 self.version = (int(major), int(minor)) 100 except IndexError: 101 self.version = (1, 0)
102
103 - def _first_line(self, line):
104 """ parse first line """ 105 self.status_line = status_line = line.strip() 106 try: 107 if self.type == 'response': 108 version, self.status = status_line.split(None, 1) 109 self._parse_version(version) 110 try: 111 self.status_int, self.reason = self.status.split(None, 1) 112 except ValueError: 113 self.status_int = self.status 114 self.status_int = int(self.status_int) 115 else: 116 method, path, version = status_line.split(None, 2) 117 self._parse_version(version) 118 self.method = method.upper() 119 self.raw_path = path 120 except ValueError: 121 raise BadStatusLine(line)
122
123 - def _parse_headerl(self, hdrs, line):
124 """ parse header line""" 125 name, value = line.split(":", 1) 126 name = name.strip().title() 127 value = value.rsplit("\r\n",1)[0].strip() 128 if name in hdrs: 129 hdrs[name] = "%s, %s" % (hdrs[name], value) 130 else: 131 hdrs[name] = value 132 return name
133 134 @property
135 - def should_close(self):
136 if self._should_close: 137 return True 138 elif self.headers_dict.get("Connection") == "close": 139 return True 140 elif self.headers_dict.get("Connection") == "Keep-Alive": 141 return False 142 elif self.version <= (1, 0): 143 return True 144 return False
145 146 @property
147 - def is_chunked(self):
148 """ is TE: chunked ?""" 149 return (self.headers_dict.get('Transfer-Encoding') == "chunked")
150 151 @property
152 - def content_len(self):
153 """ return content length as integer or 154 None.""" 155 transfert_encoding = self.headers_dict.get('Transfer-Encoding') 156 content_length = self.headers_dict.get('Content-Length') 157 if transfert_encoding != "chunked": 158 if content_length is None: 159 return 0 160 return int(content_length) 161 else: 162 return None
163
164 - def body_eof(self):
165 """do we have all the body ?""" 166 if self.is_chunked: 167 if self._chunk_eof: 168 return True 169 elif self._content_len == 0: 170 return True 171 return False
172
173 - def read_chunk(self, buf):
174 line = buf.getvalue() 175 buf2 = StringIO() 176 177 if not self.start_offset: 178 i = line.find("\r\n") 179 if i != -1: 180 chunk = line[:i].strip().split(";", 1) 181 chunk_size = int(chunk.pop(0), 16) 182 self.start_offset = i+2 183 self.chunk_size = chunk_size 184 185 if self.start_offset: 186 if self.chunk_size == 0: 187 self._chunk_eof = True 188 buf2.write(line[:self.start_offset]) 189 return '', buf2 190 else: 191 chunk = line[self.start_offset:self.start_offset+self.chunk_size] 192 end_offset = self.start_offset + self.chunk_size + 2 193 # we wait CRLF else return None 194 if buf.len >= end_offset: 195 buf2.write(line[end_offset:]) 196 self.chunk_size = 0 197 return chunk, buf2 198 return '', buf
199
200 - def trailing_header(self, buf):
201 line = buf.getvalue() 202 i = line.find("\r\n\r\n") 203 return (i != -1)
204
205 - def filter_body(self, buf):
206 """\ 207 Filter body and return a tuple: (body_chunk, new_buffer) 208 Both can be None, and new_buffer is always None if its empty. 209 """ 210 dlen = buf.len 211 chunk = '' 212 213 if self.is_chunked: 214 try: 215 chunk, buf2 = self.read_chunk(buf) 216 except Exception, e: 217 raise ParserError("chunked decoding error [%s]" % str(e)) 218 219 if not chunk: 220 return '', buf 221 else: 222 buf2 = StringIO() 223 if self._content_len > 0: 224 nr = min(dlen, self._content_len) 225 chunk = buf.getvalue()[:nr] 226 self._content_len -= nr 227 228 self.start_offset = 0 229 buf2.seek(0, 2) 230 return (chunk, buf2)
231