pythononwheels / pow_devel

development repo for PyhtonOnWheels framework
www.pythononwheels.org
MIT License
75 stars 10 forks source link

HTTPS Protocol #43

Closed jeffny2015 closed 3 years ago

jeffny2015 commented 4 years ago

Question, what is the best way to set the ssl certificates, what i guess is to add like an ssl_options with their respective paths in the config.py file and then in the server.py add the ssl options

reference: https://stackoverflow.com/questions/13471115/how-to-handle-a-request-with-https-protocol-in-tornado/13472397 http_server = tornado.httpserver.HTTPServer(applicaton, ssl_options={ "certfile": os.path.join(data_dir, "mydomain.crt"), "keyfile": os.path.join(data_dir, "mydomain.key"), })

or this is already implemented ?

pythononwheels commented 4 years ago

Normally any Native Tornado solution/approach should work. But ssl_options seems to have changed a bit in the newer versions of Tornado. They prepare a ssl_context following the tornado docs.

See here: https://www.tornadoweb.org/en/stable/_modules/tornado/httpserver.html


To make this server serve SSL traffic, send the ``ssl_options`` keyword
argument with an `ssl.SSLContext` object. For compatibility with older
versions of Python ``ssl_options`` may also be a dictionary of keyword
arguments for the `ssl.wrap_socket` method.::

ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ssl_ctx.load_cert_chain(os.path.join(data_dir, "mydomain.crt"),
                               os.path.join(data_dir, "mydomain.key"))
HTTPServer(application, ssl_options=ssl_ctx)
pythononwheels commented 4 years ago

Works fine when handed to the application. I used this to make the cert and key (taken from let's encrypt docs)

openssl req -x509 -out localhost.crt -keyout localhost.key   -newkey rsa:2048 -nodes -sha256   -subj '/CN=localhost' -extensions EXT -config <( \
   printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth")

Gives you localhost.crtand localhost,key

pythononwheels commented 4 years ago

Added an option to server_settings in config.py to handle this in the config rather than changing server.py Must run tests first. Will upload a new version 0.924a then.

Looks like this:

BASEDIR=os.path.normpath(os.path.join(os.path.dirname(__file__), ".."))
DATADIR=os.path.join(BASEDIR, "data")
CERTDIR=os.path.join(DATADIR, "certs")
LOGDIR=os.path.join(BASEDIR, "log")

server_settings = {
    "protocol"          :   "http://",      #changed automatically depending on ssl: True or False
    "host"              :   "localhost",
    "port"              :   8080,
    "debug"             :   True,
    "debug_print"       :   True,
    #Logs a stack trace if the IOLoop is blocked for more than s seconds. so 0.050 means 50ms
    "IOLoop.set_blocking_log_threshold" : 0, 
    "logging"           :   True,
    "analytics_logging" :   False,
    "template_path"     :   os.path.join(BASEDIR, "views"),
    "static_url_prefix" :   "/static/",
    "static_path"       :   os.path.join(BASEDIR, "static"),
    "login_url"         :   "/login",
    "xsrf_cookies"      :   False,
    #"log_function"      :   you can give your own log function here.
    "cookie_secret"     :   "{{cookie_secret}}",
    "ssl"               :   False,
    "ssl_options"       :   {
        "certfile"  :   os.path.abspath(os.path.join(CERTDIR, "localhost.crt")),
        "keyfile"   :   os.path.abspath(os.path.join(CERTDIR, "localhost.key"))
    }
}

New CERTDIR and ssl option. Will be automatically added when ssl = True

if app_settings["ssl"]:
        ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
        ssl_ctx.load_cert_chain(app_settings["ssl_options"]["certfile"], app_settings["ssl_options"]["keyfile"])
        app_settings["protocol"] = "https"
    else:
        app_settings["protocol"] = "http"

https runs fine on my machine ;)

pow_ssl_Screenshot from 2020-09-21 17-42-01

jeffny2015 commented 4 years ago

nice, other thing I want to handle when the user makes a request that is not set it up in the handlers, if i test for example using the routes by default:

ROUTE 0 : pattern: /+?test/+?(?P<id>\d+)$                              handler: PyTestHandler
ROUTE 1 : pattern: /+?test/+?(?P<identifier>[A-Fa-f0-9]{8}-[A-Fa-f0    handler: PyTestHandler
ROUTE 2 : pattern: /testresults(?:/?\.\w+)?/?                          handler: PyTestHandler
ROUTE 3 : pattern: /index/([0-9]+)(?:/?\.\w+)?/?                       handler: IndexdHandler
ROUTE 4 : pattern: /+?index/+?(?P<identifier>[A-Fa-f0-9]{8}-[A-Fa-f    handler: IndexdHandler
ROUTE 5 : pattern: /hello(?:/?\.\w+)?/?                                handler: HelloHandler
ROUTE 6 : pattern: /(?:/?\.\w+)?/?                                     handler: IndexdHandler
ROUTE 7 : pattern: .*(?:/?\.\w+)?/?                                    handler: ErrorHandler

and making a POST request: 'http://localhost:8080/?x=3232323' using this url i got:

Status: | 500
-- | --
Message: | (<class 'AttributeError'>, AttributeError("'IndexdHandler' object has no attribute 'model'"), <traceback object at 0x00000160CF92C200>)
Data: |  
URI: | /
Request: | HTTPServerRequest(protocol='http', host='localhost:8080', method='POST', uri='/?x=3232323', version='HTTP/1.1', remote_ip='::1')

How can i set the ErrorHandler to catch other routes i dont have in the handlers

jeffny2015 commented 4 years ago

Hi there, i dont know if am i ok but i want to handle this case for example:

image

jeffny2015 commented 4 years ago

He returns a 500 internal server error, but it doesnt return a body response, i would like to return something but the ErrorHandler doesnt catched

jeffny2015 commented 4 years ago

as a POST request he doesnt but a get request he does

jeffny2015 commented 4 years ago

Now i understand why this happen, i know this is a different issue but we are in the same thread jejeje.

image

look at the image, in handlers/base.py

when this if condition evaluates the isinstance, for example calling the Errorhandler in the shorties.py he is calling self.model, and this variable doesn't exist in the ErrorHandler class(x Class that doesn't use a model XD) so i fixed this by creating a variable Model = None in the Errorhandler, and whith this i solved the problem of no returning message =)

pythononwheels commented 4 years ago

You can just add a post method to the error handler in handlers/shorties. This is the one catching all "undefined" routes. There is already a get method defined, this is why it is catching this.

This is from handlers/shorties:

# this will be the last route since it has the lowest pos.
@app.add_route(r".*", pos=0)
class ErrorHandler(PowHandler):
    def get(self):
        return self.error( template="404.tmpl", http_status=404   )
    def post(self):
        self.write("post error")

error_handler_post

pythononwheels commented 4 years ago

The model you issues mention is already solved in success. I think I just transfer the solution. I will open a new issue and apply the change to the error method as well.

But for the handling, adding the according method (post, delete, ...) to the ErrorHandlerin shorties works out of the box.

pythononwheels commented 4 years ago

This is the result with the Model handling in self.error() fixed.

{
  "status": 500,
  "data": {
    "request": "HTTPServerRequest(protocol='https', host='localhost:8080', method='POST', uri='/shjdjsdj?x=12121212',        version='HTTP/1.1', remote_ip='127.0.0.1')"
  },
  "error": {
    "message": " HTTP Method: POST not supported for this route. "
  },
  "next": null,
  "prev": null
}

But it is of course "unhandled". If you want to catch any "undefined" route calls, just add the method to shorties/ErrorHandler and reroute or process them as you like. Including returning an error like the above.

jeffny2015 commented 4 years ago

ok

pythononwheels commented 4 years ago

You can also use the "standard" dispatch definition in ErrorHandler like this: (Directing all GET and POST to the method handle_all. you can just add more HTTP Mehthods or direct them to other methods.

# this will be the last route since it has the lowest pos.
@app.add_route(r".*", dispatch={"get" : "handle_all", "post": "handle_all"}, pos=0)
class ErrorHandler(PowHandler):
    def handle_all(self):
        self.error(http_status=404, data={ "error" : "URL not found error"}, pure=True, format="json")

Since they are all send to the same method it makes sense to use the method rotues like this:

@app.make_routes()
class ErrorHandler(PowHandler):
    @route(r".*", dispatch=["get", "post", "put", "delete"], pos=0)
    def handle_all(self):
        self.error(http_status=404, data={ "error" : "URL not found error"}, pure=True, format="json")

Result is the same but it's much shorter.

Which will lead to these routes, making handle_all()catch all undefined routes for POST and GET requests. Using the self.error() method you can set the

see the ROUTING: CLASS ROUTE (+) : route: .* handler: ErrorHandler dispatch: ['get', 'post']

issue_43_method_routes

Result when called with test URL: issue_43_handle_all