2013-05-16 26 views
4

您好我需要一些基地认证的帮助,而ajax get/post请求到python baseHTTPserver。CORS与python baseHTTPserver 501(不支持的方法('选项'))在铬

我能够在python脚本中更改一些代码行来发送CORS头文件。当我禁用http基础认证时,它在现代浏览器中正常工作。

如果验证启用,我得到一个501(不支持的方法('OPTIONS'))错误(我chrome)。

我花了几个小时寻找解决方案,现在我认为iam的一个好方法。正如我在下面的主题阅读HTTPRequestHandler可能会导致这个问题,但我的pyton技能不足以解决问题。

如果发现一些关于这个主题的帖子herehere但我不能让它与我的脚本运行。有人可以帮助我运行吗?

任何帮助或想法将不胜感激。

# Copyright 2012-2013 Eric Ptak - trouch.com 
    # 
    # Licensed under the Apache License, Version 2.0 (the "License"); 
    # you may not use this file except in compliance with the License. 
    # You may obtain a copy of the License at 
    # 
    #  http://www.apache.org/licenses/LICENSE-2.0 
    # 
    # Unless required by applicable law or agreed to in writing, software 
    # distributed under the License is distributed on an "AS IS" BASIS, 
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
    # See the License for the specific language governing permissions and 
    # limitations under the License. 

    import os 
    import threading 
    import re 
    import codecs 
    import mimetypes as mime 
    import logging 

    from webiopi.utils import * 

    if PYTHON_MAJOR >= 3: 
     import http.server as BaseHTTPServer 
    else: 
     import BaseHTTPServer 

    try : 
     import _webiopi.GPIO as GPIO 
    except: 
     pass 

    WEBIOPI_DOCROOT = "/usr/share/webiopi/htdocs" 

    class HTTPServer(BaseHTTPServer.HTTPServer, threading.Thread): 
     def __init__(self, host, port, handler, context, docroot, index, auth=None): 
      BaseHTTPServer.HTTPServer.__init__(self, ("", port), HTTPHandler) 
      threading.Thread.__init__(self, name="HTTPThread") 
      self.host = host 
      self.port = port 

      if context: 
       self.context = context 
       if not self.context.startswith("/"): 
        self.context = "/" + self.context 
       if not self.context.endswith("/"): 
        self.context += "/" 
      else: 
       self.context = "/" 

      self.docroot = docroot 

      if index: 
       self.index = index 
      else: 
       self.index = "index.html" 

      self.handler = handler 
      self.auth = auth 

      self.running = True 
      self.start() 

     def get_request(self): 
      sock, addr = self.socket.accept() 
      sock.settimeout(10.0) 
      return (sock, addr) 

     def run(self): 
      info("HTTP Server binded on http://%s:%s%s" % (self.host, self.port, self.context)) 
      try: 
       self.serve_forever() 
      except Exception as e: 
       if self.running == True: 
        exception(e) 
      info("HTTP Server stopped") 

     def stop(self): 
      self.running = False 
      self.server_close() 

    class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): 
     logger = logging.getLogger("HTTP") 

     def log_message(self, fmt, *args): 
      self.logger.debug(fmt % args) 

     def log_error(self, fmt, *args): 
      pass 

     def version_string(self): 
      return VERSION_STRING 

     def checkAuthentication(self): 
      if self.server.auth == None or len(self.server.auth) == 0: 
       return True 

      authHeader = self.headers.get('Authorization') 
      if authHeader == None: 
       return False 

      if not authHeader.startswith("Basic "): 
       return False 

      auth = authHeader.replace("Basic ", "") 
      if PYTHON_MAJOR >= 3: 
       auth_hash = encrypt(auth.encode()) 
      else: 
       auth_hash = encrypt(auth) 

      if auth_hash == self.server.auth: 
       return True 
      return False 

     def requestAuthentication(self): 
      self.send_response(401) 
      self.send_header("WWW-Authenticate", 'Basic realm="webiopi"') 
      self.end_headers(); 

     def sendResponse(self, code, body=None, type="text/plain"): 
      if code >= 400: 
       if body != None: 
        self.send_error(code, body) 
       else: 
        self.send_error(code) 
      else: 
       self.send_response(code) 
       self.send_header("Cache-Control", "no-cache") 
       self.send_header("Access-Control-Allow-Origin", "*") 
       self.send_header("Access-Control-Allow-Methods", "POST, GET") 
       self.send_header("Access-Control-Allow-Headers", " X-Custom-Header") 
       if body != None: 
        self.send_header("Content-Type", type); 
        self.end_headers(); 
        self.wfile.write(body.encode()) 

     def findFile(self, filepath): 
      if os.path.exists(filepath): 
       if os.path.isdir(filepath): 
        filepath += "/" + self.server.index 
        if os.path.exists(filepath): 
         return filepath 
       else: 
        return filepath 
      return None 


     def serveFile(self, relativePath): 
      if self.server.docroot != None: 
       path = self.findFile(self.server.docroot + "/" + relativePath) 
       if path == None: 
        path = self.findFile("./" + relativePath) 

      else: 
       path = self.findFile("./" + relativePath)     
       if path == None: 
        path = self.findFile(WEBIOPI_DOCROOT + "/" + relativePath) 

      if path == None and (relativePath.startswith("webiopi.") or relativePath.startswith("jquery")): 
       path = self.findFile(WEBIOPI_DOCROOT + "/" + relativePath) 

      if path == None: 
       return self.sendResponse(404, "Not Found") 

      realPath = os.path.realpath(path) 

      if realPath.endswith(".py"): 
       return self.sendResponse(403, "Not Authorized") 

      if not (realPath.startswith(os.getcwd()) 
        or (self.server.docroot and realPath.startswith(self.server.docroot)) 
        or realPath.startswith(WEBIOPI_DOCROOT)): 
       return self.sendResponse(403, "Not Authorized") 

      (type, encoding) = mime.guess_type(path) 
      f = codecs.open(path, encoding=encoding) 
      data = f.read() 
      f.close() 
      self.send_response(200) 
      self.send_header("Content-Type", type); 
      self.send_header("Content-Length", os.path.getsize(realPath)) 
      self.end_headers() 
      self.wfile.write(data) 

     def processRequest(self): 
      self.request.settimeout(None) 
      if not self.checkAuthentication(): 
       return self.requestAuthentication() 

      request = self.path.replace(self.server.context, "/").split('?') 
      relativePath = request[0] 
      if relativePath[0] == "/": 
       relativePath = relativePath[1:] 

      if relativePath == "webiopi" or relativePath == "webiopi/": 
       self.send_response(301) 
       self.send_header("Location", "/") 
       self.end_headers() 
       return 

      params = {} 
      if len(request) > 1: 
       for s in request[1].split('&'): 
        if s.find('=') > 0: 
         (name, value) = s.split('=') 
         params[name] = value 
        else: 
         params[s] = None 

      compact = False 
      if 'compact' in params: 
       compact = str2bool(params['compact']) 

      try: 
       result = (None, None, None) 
       if self.command == "GET": 
        result = self.server.handler.do_GET(relativePath, compact) 
       elif self.command == "POST": 
        length = 0 
        length_header = 'content-length' 
        if length_header in self.headers: 
         length = int(self.headers[length_header]) 
        result = self.server.handler.do_POST(relativePath, self.rfile.read(length), compact) 
       else: 
        result = (405, None, None) 

       (code, body, type) = result 

       if code > 0: 
        self.sendResponse(code, body, type) 
       else: 
        if self.command == "GET": 
         self.serveFile(relativePath) 
        else: 
         self.sendResponse(404) 

      except (GPIO.InvalidDirectionException, GPIO.InvalidChannelException, GPIO.SetupException) as e: 
       self.sendResponse(403, "%s" % e) 
      except ValueError as e: 
       self.sendResponse(403, "%s" % e) 
      except Exception as e: 
       self.sendResponse(500) 
       raise e 

     def do_GET(self): 
      self.processRequest() 

     def do_POST(self): 
      self.processRequest() 

回答

7

客户端应该发出请求,第一个选项,然后按GET请求。提出的解决方案并不是最优的,因为我们正在回答OPTIONS请求的内容。

def do_OPTIONS(self): 
      self.sendResponse(200) 
      self.processRequest() # not good! 

我们应该正确回答OPTIONS请求。如果我们这样做了,客户会在收到正确答案后发出GET请求。

我得到了CORS引起的501 Unsupported方法('OPTIONS')),并请求“Content-Type:application/json; charset = utf-8”。

为了解决这个错误,我在do_OPTIONS中启用了CORS,并使客户端能够请求特定的内容类型。

我的解决办法:

def do_OPTIONS(self): 
    self.send_response(200, "ok") 
    self.send_header('Access-Control-Allow-Origin', '*') 
    self.send_header('Access-Control-Allow-Methods', 'GET, OPTIONS') 
    self.send_header("Access-Control-Allow-Headers", "X-Requested-With") 
    self.send_header("Access-Control-Allow-Headers", "Content-Type") 
    self.end_headers() 

def do_GET(self): 
    self.processRequest() 
0

由于您的HTTPHandler上没有do_OPTIONS方法,因此引发错误。它将处理OPTIONS请求。我怀疑你将有进一步的问题,但是这是一个良好的开端;)

+0

嗨@morphyn,我了解,以前,但同时增加了在该HttpHandler的方法do_OPTIONS我卡住了。我想添加某物。 like def do_OPTIONS(self): self.send_response(200,“ok”) self.send_header('Access-Control-Allow-Origin','*') self.send_header('Access-Control-Allow-Methods ','GET,POST,OPTIONS') 类HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler)中的self.send_header(“Access-Control-Allow-Headers”,“X-Requested-With”):但它不起作用。 – user1532132

+0

当你说它“不起作用”,这是什么意思?错误信息是否改变? –

+0

另外,你是否有理由不使用像'Werkzeug'这样的工具来促进这类工作? –

1

得到它的工作:

阿贾克斯将发送OPTIONS请求到服务器,所以你必须添加一个do_options方法到BaseHTTPRequestHandler而无需认证(发送响应代码200)。

之后,您可以像往常一样调用处理请求的函数。

这里是我的解决方案(在Safari 6.x的检查,火狐20,Chrome26在OS X):

def do_OPTIONS(self): 
    self.sendResponse(200) 
    self.processRequest() 

你必须改变的第二件事情是,你必须添加一个响应头在processRequest函数中。添加Access-Control-Allow-Headers:授权,例如self.send_header("Access-Control-Allow-Headers", "Authorization"),允许ajax发送基础认证令牌。

工作脚本:

 # Copyright 2012-2013 Eric Ptak - trouch.com 
    # 
    # Licensed under the Apache License, Version 2.0 (the "License"); 
    # you may not use this file except in compliance with the License. 
    # You may obtain a copy of the License at 
    # 
    #  http://www.apache.org/licenses/LICENSE-2.0 
    # 
    # Unless required by applicable law or agreed to in writing, software 
    # distributed under the License is distributed on an "AS IS" BASIS, 
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
    # See the License for the specific language governing permissions and 
    # limitations under the License. 

    import os 
    import threading 
    import re 
    import codecs 
    import mimetypes as mime 
    import logging 

    from webiopi.utils import * 

    if PYTHON_MAJOR >= 3: 
     import http.server as BaseHTTPServer 
    else: 
     import BaseHTTPServer 

    try : 
     import _webiopi.GPIO as GPIO 
    except: 
     pass 

    WEBIOPI_DOCROOT = "/usr/share/webiopi/htdocs" 

    class HTTPServer(BaseHTTPServer.HTTPServer, threading.Thread): 
     def __init__(self, host, port, handler, context, docroot, index, auth=None): 
      BaseHTTPServer.HTTPServer.__init__(self, ("", port), HTTPHandler) 
      threading.Thread.__init__(self, name="HTTPThread") 
      self.host = host 
      self.port = port 

      if context: 
       self.context = context 
       if not self.context.startswith("/"): 
        self.context = "/" + self.context 
       if not self.context.endswith("/"): 
        self.context += "/" 
      else: 
       self.context = "/" 

      self.docroot = docroot 

      if index: 
       self.index = index 
      else: 
       self.index = "index.html" 

      self.handler = handler 
      self.auth = auth 

      self.running = True 
      self.start() 

     def get_request(self): 
      sock, addr = self.socket.accept() 
      sock.settimeout(10.0) 
      return (sock, addr) 

     def run(self): 
      info("HTTP Server binded on http://%s:%s%s" % (self.host, self.port, self.context)) 
      try: 
       self.serve_forever() 
      except Exception as e: 
       if self.running == True: 
        exception(e) 
      info("HTTP Server stopped") 

     def stop(self): 
      self.running = False 
      self.server_close() 

    class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): 
     logger = logging.getLogger("HTTP") 

     def log_message(self, fmt, *args): 
      self.logger.debug(fmt % args) 

     def log_error(self, fmt, *args): 
      pass 

     def version_string(self): 
      return VERSION_STRING 

     def checkAuthentication(self): 
      if self.server.auth == None or len(self.server.auth) == 0: 
       return True 

      authHeader = self.headers.get('Authorization') 
      if authHeader == None: 
       return False 

      if not authHeader.startswith("Basic "): 
       return False 

      auth = authHeader.replace("Basic ", "") 
      if PYTHON_MAJOR >= 3: 
       auth_hash = encrypt(auth.encode()) 
      else: 
       auth_hash = encrypt(auth) 

      if auth_hash == self.server.auth: 
       return True 
      return False 

     def requestAuthentication(self): 
      self.send_response(401) 
      self.send_header("WWW-Authenticate", 'Basic realm="webiopi"') 
      self.end_headers(); 

     def sendResponse(self, code, body=None, type="text/plain"): 
      if code >= 400: 
       if body != None: 
        self.send_error(code, body) 
       else: 
        self.send_error(code) 
      else: 
       self.send_response(code) 
       self.send_header("Cache-Control", "no-cache") 
       self.send_header("Access-Control-Allow-Origin", "*") 
       self.send_header("Access-Control-Allow-Methods", "POST, GET, OPTIONS") 
       self.send_header("Access-Control-Allow-Headers", "Authorization") 
       if body != None: 
        self.send_header("Content-Type", type); 
        self.end_headers(); 
        self.wfile.write(body.encode()) 

     def findFile(self, filepath): 
      if os.path.exists(filepath): 
       if os.path.isdir(filepath): 
        filepath += "/" + self.server.index 
        if os.path.exists(filepath): 
         return filepath 
       else: 
        return filepath 
      return None 


     def serveFile(self, relativePath): 
      if self.server.docroot != None: 
       path = self.findFile(self.server.docroot + "/" + relativePath) 
       if path == None: 
        path = self.findFile("./" + relativePath) 

      else: 
       path = self.findFile("./" + relativePath)     
       if path == None: 
        path = self.findFile(WEBIOPI_DOCROOT + "/" + relativePath) 

      if path == None and (relativePath.startswith("webiopi.") or relativePath.startswith("jquery")): 
       path = self.findFile(WEBIOPI_DOCROOT + "/" + relativePath) 

      if path == None: 
       return self.sendResponse(404, "Not Found") 

      realPath = os.path.realpath(path) 

      if realPath.endswith(".py"): 
       return self.sendResponse(403, "Not Authorized") 

      if not (realPath.startswith(os.getcwd()) 
        or (self.server.docroot and realPath.startswith(self.server.docroot)) 
        or realPath.startswith(WEBIOPI_DOCROOT)): 
       return self.sendResponse(403, "Not Authorized") 

      (type, encoding) = mime.guess_type(path) 
      f = codecs.open(path, encoding=encoding) 
      data = f.read() 
      f.close() 
      self.send_response(200) 
      self.send_header("Content-Type", type); 
      self.send_header("Content-Length", os.path.getsize(realPath)) 
      self.end_headers() 
      self.wfile.write(data) 

     def processRequest(self): 
      self.request.settimeout(None) 
      if not self.checkAuthentication(): 
       return self.requestAuthentication() 

      request = self.path.replace(self.server.context, "/").split('?') 
      relativePath = request[0] 
      if relativePath[0] == "/": 
       relativePath = relativePath[1:] 

      if relativePath == "webiopi" or relativePath == "webiopi/": 
       self.send_response(301) 
       self.send_header("Location", "/") 
       self.end_headers() 
       return 

      params = {} 
      if len(request) > 1: 
       for s in request[1].split('&'): 
        if s.find('=') > 0: 
         (name, value) = s.split('=') 
         params[name] = value 
        else: 
         params[s] = None 

      compact = False 
      if 'compact' in params: 
       compact = str2bool(params['compact']) 

      try: 
       result = (None, None, None) 
       if self.command == "GET": 
        result = self.server.handler.do_GET(relativePath, compact) 
       elif self.command == "POST": 
        length = 0 
        length_header = 'content-length' 
        if length_header in self.headers: 
         length = int(self.headers[length_header]) 
        result = self.server.handler.do_POST(relativePath, self.rfile.read(length), compact) 
       else: 
        result = (405, None, None) 

       (code, body, type) = result 

       if code > 0: 
        self.sendResponse(code, body, type) 
       else: 
        if self.command == "GET": 
         self.serveFile(relativePath) 
        else: 
         self.sendResponse(404) 

      except (GPIO.InvalidDirectionException, GPIO.InvalidChannelException, GPIO.SetupException) as e: 
       self.sendResponse(403, "%s" % e) 
      except ValueError as e: 
       self.sendResponse(403, "%s" % e) 
      except Exception as e: 
       self.sendResponse(500) 
       raise e 


     def do_OPTIONS(self): 
      self.sendResponse(200) 
      self.processRequest() 

     def do_GET(self): 
      self.processRequest() 

     def do_POST(self): 
      self.processRequest()