嗨我需要一些基本身份验证的帮助,而ajax get / post请求到python baseHTTPserver。
我能够在python脚本中更改一些代码行来发送CORS头。当我禁用http base authentification时,它在现代浏览器中工作正常。
如果启用了身份验证,我会收到501(不支持的方法('OPTIONS'))错误(i chrome)。
我花了好几个小时寻找解决方案,现在我认为我的方式很好。正如我在下面的主题中所读到的,HTTPRequestHandler可能会导致问题,但我的pyton技能不足以解决问题。
如果发现了一些关于此主题here和here的帖子,但我无法使用我的脚本运行它。有人可以帮助我让它运行吗?
任何帮助或想法都将受到高度赞赏。
# 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()
答案 0 :(得分:11)
客户端应发出两个请求,首先是一个OPTIONS,然后是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()
答案 1 :(得分:1)
搞定了:
Ajax会向服务器发送OPTIONS
请求,因此您必须向do_options
添加BaseHTTPRequestHandler
方法,而不进行身份验证(发送响应代码200)。
之后,您可以像往常一样调用函数来处理请求。
这是我的解决方案(在Safari 6.x,Firefox 20,OS X上的Chrome26中检查):
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()
答案 2 :(得分:0)
由于您的do_OPTIONS
上没有HTTPHandler
方法,因此引发了错误。它将处理OPTIONS
个请求。我怀疑你会有进一步的问题,但这是一个好的开始;)