2011-11-03 23 views
6

Tôi đã triển khai mô-đun API theo dõi Pivotal trong Python 2.7. Pivotal Tracker API dự kiến ​​dữ liệu POST là một tài liệu XML và "application/xml" là loại nội dung.Làm cách nào để đăng ký tự không phải ASCII bằng cách sử dụng httplib khi loại nội dung là "application/xml"

Mã của tôi sử dụng urlib/httplib để gửi tài liệu như:

request = urllib2.Request(self.url, xml_request.toxml('utf-8') if xml_request else None, self.headers) 
    obj = parse_xml(self.opener.open(request)) 

Điều này mang lại một ngoại lệ khi văn bản XML chứa các ký tự phi ASCII:

File "/usr/lib/python2.7/httplib.py", line 951, in endheaders 
    self._send_output(message_body) 
File "/usr/lib/python2.7/httplib.py", line 809, in _send_output 
    msg += message_body 
exceptions.UnicodeDecodeError: 'ascii' codec can't decode byte 0xc5 in position 89: ordinal not in range(128) 

Như gần như tôi có thể xem, httplib._send_output đang tạo một chuỗi ASCII cho tải trọng tin nhắn, có lẽ vì nó hy vọng dữ liệu được mã hóa URL (application/x-www-form-urlencoded). Nó hoạt động tốt với ứng dụng/xml miễn là chỉ sử dụng các ký tự ASCII.

Có cách nào đơn giản để đăng dữ liệu ứng dụng/xml chứa ký tự không phải ASCII hay tôi sẽ phải nhảy qua các vòng (ví dụ: sử dụng Twistd và nhà sản xuất tùy chỉnh cho tải trọng POST)?

Trả lời

7

Bạn đang trộn Unicode và bytestrings.

>>> msg = u'abc' # Unicode string 
>>> message_body = b'\xc5' # bytestring 
>>> msg += message_body 
Traceback (most recent call last): 
    File "<input>", line 1, in <module> 
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc5 in position 0: ordinal \ 
not in range(128) 

Để khắc phục điều đó, chắc chắn có nghĩa rằng self.headers nội dung được mã hóa đúng cách, tất cả các phím, giá trị trong headers nên bytestrings:

self.headers = dict((k.encode('ascii') if isinstance(k, unicode) else k, 
        v.encode('ascii') if isinstance(v, unicode) else v) 
        for k,v in self.headers.items()) 

Lưu ý: mã hóa ký tự của tiêu đề không có gì để làm với một mã hóa ký tự của một ví dụ cơ thể, văn bản xml có thể được mã hóa độc lập (nó chỉ là một luồng octet từ quan điểm của thông điệp http).

Điều tương tự cũng xảy ra với self.url —nếu nó có loại unicode; chuyển đổi nó thành một bytestring (sử dụng 'ascii' character encoding).


HTTP message consists of a start-line, "headers", an empty line and possibly a message-body nên self.headers được sử dụng cho tiêu đề, self.url được sử dụng để khởi động dòng (phương pháp http goes here) và có lẽ cho Host tiêu đề http (nếu khách hàng là http/1.1), văn bản XML đi vào nội dung thư (dưới dạng blob nhị phân).

Luôn sử dụng mã hóa ASCII an toàn cho self.url (IDNA có thể được sử dụng cho tên miền không phải ascii — kết quả cũng là ASCII).

Đây là những gì rfc 7230 says about http headers character encoding:

Về mặt lịch sử, HTTP đã cho phép nội dung lĩnh vực với văn bản bằng charset ISO-8859-1 [ISO-8859-1], hỗ trợ bảng mã khác chỉ qua sử dụng [RFC2047 ] mã hóa. Trong thực tế, hầu hết các tiêu đề HTTP giá trị trường chỉ sử dụng một tập con của bộ ký tự US-ASCII [USASCII]. Trường tiêu đề được xác định mới NÊN giới hạn giá trị trường của chúng thành octet US-ASCII. Người nhận NÊN xử lý các octet khác trong trường nội dung (obs-text) dưới dạng dữ liệu mờ.

Để chuyển đổi XML để một bytestring, xem application/xml encoding condsiderations:

Việc sử dụng UTF-8, mà không có một BOM, được khuyến khích cho tất cả các đơn vị MIME XML.

+0

Có lẽ bạn có thể thay đổi 'loại nội dung' của tiêu đề, nhưng cách này khắc phục được sự cố? 'Msg' được xây dựng trong thư viện python và là chuỗi byte. – jro

+1

@jro: Nó không liên quan gì đến HTTP. Hãy xem ví dụ * hoàn chỉnh * ở trên. – jfs

+0

Tôi hiểu rằng điều này gây ra vấn đề, nhưng quan điểm của tôi là anh ta không kiểm soát biến 'msg'. Tôi đồng ý với quan điểm của bạn, nhưng câu hỏi của tôi là nhiều hơn trong dòng của thực tế này có thể giúp anh ta để giải quyết nó khi trong libs 'msg' được tạo ra như là' msg = "\ r \ n" .join (self._buffer) '? – jro

2

Kiểm tra xem self.url có phải là unicode hay không. Nếu đó là unicode, thì httplib sẽ coi dữ liệu là unicode.

bạn có thể buộc encode self.url để unicode, sau đó httplib sẽ đối xử với tất cả các dữ liệu như unicode

0

Có 3 điều cần được đề cập ở đây

  • Non Unicode chuỗi + chuỗi Unicode, kết quả sẽ được chuyển thành chuỗi Unicode tự động.
  • Python 2,7 httplib, chỉ cần sử dụng + để tham gia tiêu đề với nội dung mà tôi không nghĩ là một phương pháp hay, chúng tôi không nên tin tưởng vào việc chuyển đổi loại tự động. nhưng Python 2.6 httplib thì khác. tiêu chuẩn giao thức
  • HTTP gợi ý ISO-8859-1 mã hóa cho tiêu đề, nhưng nếu bạn muốn đưa phi ISO-8859-1 ký tự, bạn phải mã hóa nó như rfc2047 mô tả

Các đơn giản giải pháp là mã hóa chặt chẽ cả tiêu đề và nội dung thành utf-8 trước khi gửi đi.

1

Tương tự như JF Sebastian câu trả lời, nhưng tôi thêm một cái mới để mã định dạng công trình (và nhiều google-thể)

Đây là những gì sẽ xảy ra nếu bạn đang cố gắng để gắn thẻ vào cuối yêu cầu biểu mẫu cơ giới hóa:

br = mechanize.Browser() 
br.select_form(nr=0) 
br['form_thingy'] = u"Wonderful" 
headers = dict((k.encode('ascii') if isinstance(k, unicode) else k, v.encode('ascii') if isinstance(v, unicode) else v) for k,v in br.request.headers.items()) 
br.addheaders = headers 
req = br.submit()