【问题标题】:CORS with python baseHTTPserver 501 (Unsupported method ('OPTIONS')) in chromechrome中带有python baseHTTPserver 501(不支持的方法('OPTIONS'))的CORS
【发布时间】:2013-05-11 03:12:06
【问题描述】:

您好,在向 python baseHTTPserver 发送 ajax 获取/发布请求时,我需要一些基本身份验证方面的帮助。

我能够更改用于发送 CORS 标头的 python 脚本中的一些代码行。当我禁用 http 基本身份验证时,它在现代浏览器中运行良好。

如果启用了身份验证,我会收到 501(不支持的方法('OPTIONS'))错误(i chrome)。

我花了几个小时寻找解决方案,现在我认为我的方法很好。正如我在下面的主题中阅读的那样,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()

【问题讨论】:

    标签: jquery python ajax cors webiopi


    【解决方案1】:

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

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

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

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

    为了解决这个错误,我在 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()
    

    【讨论】:

      【解决方案2】:

      搞定了:

      Ajax 会向服务器发送一个OPTIONS 请求,因此您必须在没有身份验证的情况下向BaseHTTPRequestHandler 添加一个do_options 方法(发送响应代码200)。

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

      这是我的解决方案(在 OS X 上的 Safari 6.x、Firefox 20、Chrome26 中检查):

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

      您必须更改的第二件事是您必须在processRequest 函数中添加响应标头。添加 Access-Control-Allow-Headers:Authorization 例如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()
      

      【讨论】:

        【解决方案3】:

        引发错误是因为您的 HTTPHandler 上没有 do_OPTIONS 方法。它将处理OPTIONS 请求。我怀疑你会遇到更多问题,但这是一个好的开始;)

        【讨论】:

        • 嗨@morphyn,我之前读到过,但是在HTTPHandler中添加do_OPTIONS方法时我卡住了。我想过加点什么。像 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') self.send_header("Access-Control-Allow-Headers", "X-Requested-With") 在类 HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): 但它不起作用。
        • 当你说它“不起作用”时,它是什么意思?错误信息有变化吗?
        • 另外,你有理由不使用Werkzeug这样的工具来促进这种工作吗?
        • 很抱歉,因为蜜蜂如此不精确(我通常不喜欢这样的行为,但有时我发现自己这样做了......)“不起作用”在我的情况下意味着服务器停止工作并且没有不给任何回应。我通常不使用 python,所以我没有太多关于调试的信息。正如我所见,Werkzeug 对我不起作用,因为我必须使用 python 3.2 并且 werkzeug 最多支持 2.7。 do_OPTIONS 方法必须在基本认证之前还是之后调用?
        • 如果它停止工作,您的控制台中应该会显示一条错误消息。正确的 ?你的代码很长而且很复杂,我很难通过阅读它来调试它;)
        猜你喜欢
        • 2015-12-29
        • 1970-01-01
        • 2011-03-15
        • 2019-07-13
        • 2019-05-05
        • 2018-02-11
        • 1970-01-01
        • 2014-08-12
        • 2016-07-17
        相关资源
        最近更新 更多