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   
  7  import urlparse 
  8   
  9  from restkit.errors import BadStatusLine, ParserError 
 10  from restkit.util import normalize_name 
11 12 -class Parser(object):
13
14 - def __init__(self, ptype='response', should_close=False):
15 self.status_line = "" 16 self.status_int = None 17 self.reason = "" 18 self.status = "" 19 self.headers = [] 20 self.headers_dict = {} 21 self.raw_version = "HTTP/1.0" 22 self.raw_path = "" 23 self.version = (1,0) 24 self.method = "" 25 self.path = "" 26 self.query_string = "" 27 self.fragment = "" 28 self._content_len = None 29 self.start_offset = 0 30 self.chunk_size = 0 31 self._chunk_eof = False 32 self.type = ptype 33 self._should_close = should_close
34 35 @classmethod
36 - def parse_response(cls, should_close=False):
37 return cls(should_close=should_close)
38 39 @classmethod
40 - def parse_request(cls):
41 return cls(ptype='request')
42
43 - def filter_headers(self, headers, buf):
44 """ take a string as buffer and an header dict 45 (empty or not). It return new position or -1 46 if parsing isn't done. headers dict is updated 47 with new headers. 48 """ 49 50 s = "".join(buf) 51 i = s.find("\r\n\r\n") 52 if i != -1: 53 r = s[:i] 54 pos = i+4 55 return self.finalize_headers(headers, r, pos) 56 return -1
57
58 - def finalize_headers(self, headers, headers_str, pos):
59 """ parse the headers """ 60 lines = headers_str.split("\r\n") 61 62 # parse first line of headers 63 self._first_line(lines.pop(0)) 64 65 # parse headers. We silently ignore 66 # bad headers' lines 67 68 _headers = {} 69 hname = "" 70 for line in lines: 71 if line == "\t": 72 headers[hname] += line.strip() 73 else: 74 try: 75 hname =self._parse_headerl(_headers, line) 76 except ValueError: 77 # bad headers 78 pass 79 self.headers_dict = _headers 80 headers.extend(list(_headers.items())) 81 self.headers = headers 82 self._content_len = int(_headers.get('Content-Length',0)) 83 if self.type == 'request': 84 (_, _, self.path, self.query_string, self.fragment) = \ 85 urlparse.urlsplit(self.raw_path) 86 return pos
87
88 - def _first_line(self, line):
89 """ parse first line """ 90 self.status_line = status_line = line.strip() 91 92 try: 93 if self.type == 'response': 94 version, self.status = status_line.split(" ", 1) 95 else: 96 method, path, version = status_line.split(" ") 97 except ValueError: 98 return 99 100 version = version.strip() 101 self.raw_version = version 102 try: 103 major, minor = version.split("HTTP/")[1].split(".") 104 version = (int(major), int(minor)) 105 except IndexError: 106 version = (1, 0) 107 108 self.version = version 109 110 if self.type == 'response': 111 try: 112 try: 113 self.status_int, self.reason = self.status.split(" ", 1) 114 except ValueError: 115 self.status_int = self.status 116 self.status_int = int(self.status_int) 117 except ValueError: 118 raise BadStatusLine("can't find status code") 119 else: 120 self.method = method.upper() 121 self.raw_path = path
122
123 - def _parse_headerl(self, hdrs, line):
124 """ parse header line""" 125 name, value = line.split(":", 1) 126 name = normalize_name(name.strip()) 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, data):
174 s = "".join(data) 175 if not self.start_offset: 176 i = s.find("\r\n") 177 if i != -1: 178 chunk = s[:i].strip().split(";", 1) 179 chunk_size = int(chunk.pop(0), 16) 180 self.start_offset = i+2 181 self.chunk_size = chunk_size 182 183 if self.start_offset: 184 if self.chunk_size == 0: 185 self._chunk_eof = True 186 ret = '', data[:self.start_offset] 187 return ret 188 else: 189 chunk = s[self.start_offset:self.start_offset+self.chunk_size] 190 end_offset = self.start_offset + self.chunk_size + 2 191 # we wait CRLF else return None 192 if len(data) >= end_offset: 193 ret = chunk, data[end_offset:] 194 self.chunk_size = 0 195 return ret 196 return '', data
197
198 - def trailing_header(self, data):
199 s = "".join(data) 200 i = s.find("\r\n\r\n") 201 return (i != -1)
202
203 - def filter_body(self, data):
204 """\ 205 Filter body and return a tuple: (body_chunk, new_buffer) 206 Both can be None, and new_buffer is always None if its empty. 207 """ 208 dlen = len(data) 209 chunk = '' 210 if self.is_chunked: 211 try: 212 chunk, data = self.read_chunk(data) 213 except Exception, e: 214 raise ParserError("chunked decoding error [%s]" % str(e)) 215 216 if not chunk: 217 return '', data 218 else: 219 if self._content_len > 0: 220 nr = min(dlen, self._content_len) 221 chunk = "".join(data[:nr]) 222 self._content_len -= nr 223 data = [] 224 225 self.start_offset = 0 226 return (chunk, data)
227