diff --git a/.gitignore b/.gitignore index b53a6de..2c57d08 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ # common .idea/ *.pyc -example/ +/example/* +/example/game/game_record.json .cache/ __pycache__/ @@ -9,3 +10,4 @@ __pycache__/ build/ dist/ *.egg-info +!/example/game \ No newline at end of file diff --git a/README.md b/README.md index 306144d..d80a6c4 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,9 @@ # Super Fast HTTP2 Framework for Progressive Web Application # Installation -Clone this project to your local directory. In this directory, -``` -python setup.py -``` -This will automatically install `hyper2web` to the associated Python as a site-package. +At this point, the best way to install it is probably download the zip and extract it to your projects' directory. + +I have not figured out how to make it installable with `pip` yet. I will make it available on PyPi once I have the first release. @@ -56,6 +54,9 @@ from hyper2web import app app.App(port=5000).up() ``` +# Example +See the example folders for examples. + # Test Python modules/packages and imports are confusing. You have to do ```python diff --git a/example/game/app.py b/example/game/app.py new file mode 100644 index 0000000..8995e10 --- /dev/null +++ b/example/game/app.py @@ -0,0 +1,31 @@ +import json + +from curio import aopen + +from hyper2web import app +from game import update_record, game_record, game_record_path + + +app = app.App() +# should raise an error if no response method is called +# should raise an error if response method is not called with await +async def post_record(http, stream, para): + record = json.loads(str(stream.data, encoding='utf8')) + update_record(record, game_record) + await http.send_error(stream, 200) + + # write records to disk + async with aopen(game_record_path, mode='w') as f: + game_record_string = json.dumps(game_record, indent='\t') + await f.write(game_record_string) + +app.post('/post_record', post_record) + + +async def get_top10(http, stream, para): + level_top10 = game_record.get(para['levelIndex'], []) + string = json.dumps(level_top10) + await http.send_and_end(stream, bytes(string, encoding='utf8')) +app.get('/get_top10/{levelIndex}', get_top10) + +app.up() diff --git a/example/game/game.py b/example/game/game.py new file mode 100644 index 0000000..7e04256 --- /dev/null +++ b/example/game/game.py @@ -0,0 +1,31 @@ +import json + + +def update_record(this_record, old_records): + timeUsed = this_record['timeUsed'] + + levelIndex = str(this_record['level']) + level_top10 = old_records.get(levelIndex, []) + + i = 0 + while i < 10 and i < len(level_top10): + if timeUsed < level_top10[i]['timeUsed']: + level_top10.insert(i, this_record) + break + i += 1 + else: + level_top10.insert(i, this_record) + if len(level_top10) > 10: + level_top10.pop() + + old_records[levelIndex] = level_top10 + + +game_record_path = 'game_record.json' + +try: + with open(game_record_path, encoding='utf8') as f: + game_record = json.load(f, encoding='utf8') +except json.decoder.JSONDecodeError and FileNotFoundError: + print('init a new record') + game_record = {} diff --git a/example/game/localhost.crt.pem b/example/game/localhost.crt.pem new file mode 100644 index 0000000..ebc67ef --- /dev/null +++ b/example/game/localhost.crt.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDhTCCAm2gAwIBAgIJAOrxh0dOYJLdMA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xNTA5MTkxNDE2 +NDRaFw0xNTEwMTkxNDE2NDRaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21l +LVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNV +BAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMqt +A1iu8EN00FU0eBcBGlLVmNEgV7Jkbukra+kwS8j/U2y50QPGJc/FiIVDfuBqk5dL +ACTNc6A/FQcXvWmOc5ixmC3QKKasMpuofqKz0V9C6irZdYXZ9rcsW0gHQIr989yd +R+N1VbIlEVW/T9FJL3B2UD9GVIkUELzm47CSOWZvAxQUlsx8CUNuUCWqyZJoqTFN +j0LeJDOWGCsug1Pkj0Q1x+jMVL6l6Zf6vMkLNOMsOsWsxUk+0L3tl/OzcTgUOCsw +UzY59RIi6Rudrp0oaU8NuHr91yiSqPbKFlX10M9KwEEdnIpcxhND3dacrDycj3ux +eWlqKync2vOFUkhwiaMCAwEAAaNQME4wHQYDVR0OBBYEFA0PN+PGoofZ+QIys2Jy +1Zz94vBOMB8GA1UdIwQYMBaAFA0PN+PGoofZ+QIys2Jy1Zz94vBOMAwGA1UdEwQF +MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAEplethBoPpcP3EbR5Rz6snDDIcbtAJu +Ngd0YZppGT+P0DYnPJva4vRG3bb84ZMSuppz5j67qD6DdWte8UXhK8BzWiHzwmQE +QmbKyzzTMKQgTNFntpx5cgsSvTtrHpNYoMHzHOmyAOboNeM0DWiRXsYLkWTitLTN +qbOpstwPubExbT9lPjLclntShT/lCupt+zsbnrR9YiqlYFY/fDzfAybZhrD5GMBY +XdMPItwAc/sWvH31yztarjkLmld76AGCcO5r8cSR/cX98SicyfjOBbSco8GkjYNY +582gTPkKGYpStuN7GNT5tZmxvMq935HRa2XZvlAIe8ufp8EHVoYiF3c= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/example/game/localhost.key b/example/game/localhost.key new file mode 100644 index 0000000..a114ad6 --- /dev/null +++ b/example/game/localhost.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAyq0DWK7wQ3TQVTR4FwEaUtWY0SBXsmRu6Str6TBLyP9TbLnR +A8Ylz8WIhUN+4GqTl0sAJM1zoD8VBxe9aY5zmLGYLdAopqwym6h+orPRX0LqKtl1 +hdn2tyxbSAdAiv3z3J1H43VVsiURVb9P0UkvcHZQP0ZUiRQQvObjsJI5Zm8DFBSW +zHwJQ25QJarJkmipMU2PQt4kM5YYKy6DU+SPRDXH6MxUvqXpl/q8yQs04yw6xazF +ST7Qve2X87NxOBQ4KzBTNjn1EiLpG52unShpTw24ev3XKJKo9soWVfXQz0rAQR2c +ilzGE0Pd1pysPJyPe7F5aWorKdza84VSSHCJowIDAQABAoIBACp+nh4BB/VMz8Wd +q7Q/EfLeQB1Q57JKpoqTBRwueSVai3ZXe4CMEi9/HkG6xiZtkiZ9njkZLq4hq9oB +2z//kzMnwV2RsIRJxI6ohGy+wR51HD4BvEdlTPpY/Yabpqe92VyfSYxidKZWaU0O +QMED1EODOw4ZQ+4928iPrJu//PMB4e7TFao0b9Fk/XLWtu5/tQZz9jsrlTi1zthh +7n+oaGNhfTeIJJL4jrhTrKW1CLHXATtr9SJlfZ3wbMxQVeyj2wUlP1V0M6kBuhNj +tbGbMpixD5iCNJ49Cm2PHg+wBOfS3ADGIpi3PcGw5mb8nB3N9eGBRPhLShAlq5Hi +Lv4tyykCgYEA8u3b3xJ04pxWYN25ou/Sc8xzgDCK4XvDNdHVTuZDjLVA+VTVPzql +lw7VvJArsx47MSPvsaX/+4hQXYtfnR7yJpx6QagvQ+z4ludnIZYrQwdUmb9pFL1s +8UNj+3j9QFRPenIiIQ8qxxNIQ9w2HsVQ8scvc9CjYop/YYAPaQyHaL8CgYEA1ZSz +CR4NcpfgRSILdhb1dLcyw5Qus1VOSAx3DYkhDkMiB8XZwgMdJjwehJo9yaqRCLE8 +Sw5znMnkfoZpu7+skrjK0FqmMpXMH9gIszHvFG8wSw/6+2HIWS19/wOu8dh95LuC +0zurMk8rFqxgWMWF20afhgYrUz42cvUTo10FVB0CgYEAt7mW6W3PArfUSCxIwmb4 +VmXREKkl0ATHDYQl/Cb//YHzot467TgQll883QB4XF5HzBFurX9rSzO7/BN1e6I0 +52i+ubtWC9xD4fUetXMaQvZfUGxIL8xXgVxDWKQXfLiG54c8Mp6C7s6xf8kjEUCP +yR1F0SSA/Pzb+8RbY0p7eocCgYA+1rs+SXtHZev0KyoYGnUpW+Uxqd17ofOgOxqj +/t6c5Z+TjeCdtnDTGQkZlo/rT6XQWuUUaDIXxUbW+xEMzj4mBPyXBLS1WWFvVQ5q +OpzO9E/PJeqAH6rkof/aEelc+oc/zvOU1o9uA+D3kMvgEm1psIOq2RHSMhGvDPA0 +NmAk+QKBgQCwd1681GagdIYSZUCBecnLtevXmIsJyDW2yR1NNcIe/ukcVQREMDvy +5DDkhnGDgnV1D5gYcXb34g9vYvbfTnBMl/JXmMAAG1kIS+3pvHyN6f1poVe3yJV1 +yHVuvymnJxKnyaV0L3ntepVvV0vVNIkA3oauoUTLto6txBI+b/ImDA== +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/example/game/public/game.js b/example/game/public/game.js new file mode 100644 index 0000000..334f070 --- /dev/null +++ b/example/game/public/game.js @@ -0,0 +1,352 @@ +const Game = (function() { + + //used as a static factory + let MagicalPoint = { + construct: function(x, y, radius) { + let shape = new createjs.Shape(); + shape.graphics.beginFill(createjs.Graphics.getRGB(0,0,0)); + shape.graphics.drawCircle(0, 0, radius); + shape.x = x; + shape.y = y; + return shape; + }, + + constructElectron: function(x, y, radius) { + let shape = new createjs.Shape(); + shape.graphics.beginFill(createjs.Graphics.getRGB(200,0,200)); + shape.graphics.drawCircle(0, 0, radius); + shape.x = x; + shape.y = y; + return shape; + }, + + constructNucleus: function(x, y, radius) { + let shape = new createjs.Shape(); + shape.graphics.beginFill(createjs.Graphics.getRGB(0,255,255)); + shape.graphics.drawCircle(0, 0, radius); + shape.x = x; + shape.y = y; + return shape; + }, + + onKeyBoard: function(event) { + let KEYCODE_LEFT = 37, + KEYCODE_RIGHT = 39, + KEYCODE_UP = 38, + KEYCODE_DOWN = 40; + + console.log(Animator.direction); + switch(event.keyCode) { + case KEYCODE_LEFT: + //gotten.to({x: shape.x-distance}, time); + Animator.direction.x -= 1; + break; + case KEYCODE_RIGHT: + //gotten.to({x: shape.x+distance}, time); + Animator.direction.x += 1; + break; + case KEYCODE_UP: + // gotten.to({y: shape.y-distance}, time); + Animator.direction.y -= 1; + break; + case KEYCODE_DOWN: + // gotten.to({y: shape.y+distance}, time); + Animator.direction.y += 1; + break; + } + } + }; + + //used as a static singleton + let Animator = { + init: function(stage) { + console.log('Animator.init()'); + this.stage = stage; + this.userPoint = undefined; + this.electrons = undefined; + this.nucleus = undefined; + Animator.direction = {x: 0, y:0}; + }, + + findPosition: function(center, radius, xth, totalNumber) { + let radian = Math.PI*2/totalNumber * xth; + let cos = Math.cos(radian); + let sin = Math.sin(radian); + console.log(cos, sin); + let x = radius * cos + center.x; + let y = radius * sin + center.y; + console.log(x, y); + return { + x: x, + y: y + }; + }, + + moveUserPoint: function() { + + if(this.userPoint.x > 800) { + this.userPoint.x = 0; + } + if(this.userPoint.x < 0) { + this.userPoint.x = 800; + } + if(this.userPoint.y > 600) { + this.userPoint.y = 0; + } + if(this.userPoint.y < 0) { + this.userPoint.y = 600; + } + this.userPoint.x += Animator.direction.x; + this.userPoint.y += Animator.direction.y; + }, + + /* + electron: Shape + nucleus: Shape + distance: number, angular distance in radians + time: number + */ + orbiting: function(electron, nucleus, distance) { + console.log('Animator.orbiting()'); + let radius = Math.sqrt( + (nucleus.x-electron.x)*(nucleus.x-electron.x) + + (nucleus.y-electron.y)*(nucleus.y-electron.y)); + + let x1 = electron.x - nucleus.x; + let y1 = electron.y - nucleus.y; + + let cos1 = x1/radius; + let sin1 = y1/radius; + let radian = Math.acos(cos1); + if(y1 < 0) { + radian += Math.PI; + } + + createjs.Ticker.addEventListener("tick", function() { + //bring the whole coordinates down to 0 centered + radian += distance; + + let cos2 = Math.cos(radian); + let sin2 = Math.sin(radian); + + let x2 = radius*cos2; + let y2 = radius*sin2; + + electron.x = x2 + nucleus.x; + electron.y = y2 + nucleus.y; + + Animator.stage.update(); + }); + }, + + collisionDetection: function(shape, otherShapes) { + otherShapes.forEach(function(item, index, array) { + if(Animator.isCollided(shape, item)) { + Game.handleCollision(); + } + }); + }, + + //both shape1 and shape2 have to be circles + isCollided: function(shape1, shape2) { + let r1 = shape1.graphics.command.radius; + let r2 = shape2.graphics.command.radius; + + let x1 = shape1.x; + let y1 = shape1.y; + let x2 = shape2.x; + let y2 = shape2.y; + + let distance = Math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2)); + + if(distance < (r1+r2)) { + console.log(x1, y1); + console.log(x2, y2); + return true; + } else { + return false; + } + } + }; + + //used as static singleton + let Game = { + init: function() { + this.currentLevel = 0; + this.levels = [ + { + name: 'Hydrogen', + electron: [1] + }, + { + name: 'Helium', + electron: [2] + }, + { + name: 'Lithium', + electron: [2,1] + }, + { + name: 'Beryllium', + electron: [2,2] + }, + { + name: 'Baron', + electron: [2,3] + }, + { + name: 'Carbon', + electron: [2,4] + }, + { + name: 'Nitrogen', + electron: [2,5] + }, + { + name: 'Oxygen', + electron: [2,6] + }, + { + name: 'Fluorine', + electron: [2,7] + }, + { + name: 'Neon', + electron: [2,8] + } + ]; + this.startPos = { + x: 400, + y: 550, + size: 10 + }; + this.nucleusPos = { + x: 400, + y: 250, + size: 25 + }; + }, + + run: function() { + let stage = new createjs.Stage("game_canvas"); + Animator.init(stage); + Game.currentLevel = 0; + this.initLevel(Game.currentLevel); + }, + + initLevel: function(levelIndex) { + console.log('Game.initLevel('+this.currentLevel+')'); + + Animator.userPoint = undefined; + Animator.electrons = undefined; + Animator.nucleus = undefined; + Animator.direction = {x: 0, y:0}; + + // createjs.Ticker.removeAllEventListeners(); + createjs.Tween.removeAllTweens(); + Animator.stage.removeAllChildren(); + + //get current level + let level = this.levels[levelIndex]; + + //draw target name + let targetText = level.name; + let text = new createjs.Text(targetText, "20px Arial", "#ff7700"); + text.x = 363; + text.y = 50; + text.textBaseline = "alphabetic"; + Animator.stage.addChild(text); + Animator.targetText = text; + + //init nucleus + Animator.nucleus = MagicalPoint.constructNucleus(this.nucleusPos.x, this.nucleusPos.y, this.nucleusPos.size); + Animator.stage.addChild(Animator.nucleus); + + let numberOfOrbits = level.electron.length; + Animator.electrons = []; + //init electrons + for(let i = 0; i < numberOfOrbits; i++) { + let electronsPerOrbit = level.electron[i]; + let nthOrbit = i+1; + let radius = nthOrbit*50; + for(let j = 0; j < electronsPerOrbit; j++) { + let pos = Animator.findPosition(Animator.nucleus, radius, j, electronsPerOrbit); + let electron = MagicalPoint.constructElectron(pos.x, pos.y, 5); + //console.log(pos.x, pos.y); + Animator.orbiting(electron, Animator.nucleus, nthOrbit*0.06/(nthOrbit*2-1)); + Animator.stage.addChild(electron); + Animator.electrons.push(electron); + } + } + + //the point gamer controls + Animator.userPoint = MagicalPoint.construct(this.startPos.x, this.startPos.y, this.startPos.size); + Animator.stage.addChild(Animator.userPoint); + document.onkeydown = MagicalPoint.onKeyBoard; + + Animator.stage.update(); + //register animation events + createjs.Ticker.setFPS(30); + createjs.Ticker.addEventListener("tick", function() { + Animator.moveUserPoint(); + Animator.collisionDetection(Animator.userPoint, Animator.electrons); + Game.checkGameState(); + }); + createjs.Ticker.addEventListener("tick", Animator.stage); + + // set up timer + this.levels[this.currentLevel].startTime = Date.now(); + // get top10 of this level from server + Service.get_top10(this.currentLevel) + }, + + goToNextLevel: function() { + console.log('Game.goToNextLevel()'); + if(this.currentLevel < this.levels.length-1) { + this.currentLevel++; + this.initLevel(this.currentLevel); + } else { + //finished the game + createjs.Ticker.removeAllEventListeners(); + createjs.Tween.removeAllTweens(); + let text = new createjs.Text("Congratulations! Last Level 达成!", "40px Arial", "000000"); + text.x = 140; + text.y = 200; + text.textBaseline = "alphabetic"; + Animator.stage.addChild(text); + } + }, + + isLevelCompleted: function() { + //console.log('Game.isLevelCompleted()'); + return Animator.isCollided(Animator.nucleus, Animator.userPoint); + }, + + checkGameState: function() { + //console.log('Game.checkGameState()'); + if(Game.isLevelCompleted()) { + // todo: post time record to server + let timeUsed = Date.now() - this.levels[this.currentLevel].startTime; + Service.post_record({ + level: this.currentLevel, + timeUsed: timeUsed, + user: "Master" + }); + Game.goToNextLevel(); + } + }, + + handleCollision: function() { + console.log('Game.handleCollision()'); + createjs.Ticker.removeAllEventListeners(); + createjs.Tween.removeAllTweens(); + let text = new createjs.Text("You Died", "40px Arial", "000000"); + text.x = 200; + text.y = 200; + text.textBaseline = "alphabetic"; + Animator.stage.addChild(text); + } + }; + + return Game; +})(); diff --git a/example/game/public/index.css b/example/game/public/index.css new file mode 100644 index 0000000..9f8b9eb --- /dev/null +++ b/example/game/public/index.css @@ -0,0 +1,14 @@ +#about_this_app { + position: fixed; + bottom: 0; + width: 100%; + text-align: center; +} + +#game_canvas { + border:solid 1px black; +} + +h3 { + margin: 10px; +} \ No newline at end of file diff --git a/example/game/public/index.html b/example/game/public/index.html new file mode 100644 index 0000000..71fd45a --- /dev/null +++ b/example/game/public/index.html @@ -0,0 +1,40 @@ + + + + + 元素大师 + + + + + + + + + + + + + +
+

天地英雄榜

+ +
+ +
+

Use Arrow Key to control the point

+

Your Goal is to reach the center, avoiding blue electrons

+
+ +
+ This application is served by + Hyper2Web + , an enjoyable Python web framework written by me. +
+ + + \ No newline at end of file diff --git a/example/game/public/index.js b/example/game/public/index.js new file mode 100644 index 0000000..8ef4acf --- /dev/null +++ b/example/game/public/index.js @@ -0,0 +1,15 @@ +let game_ui = new Vue({ + el: '#game_ui', + data: {} +}); + +let top10_list = new Vue({ + el: "#天地英雄榜", + data: {top10: undefined} +}); + +// get top10. This is an inferior solution. +Service.top10_list_vue = top10_list; + +Game.init(); +Game.run(); diff --git a/example/game/public/service.js b/example/game/public/service.js new file mode 100644 index 0000000..bc9721f --- /dev/null +++ b/example/game/public/service.js @@ -0,0 +1,24 @@ +const Service = (function() { + return { + post_record: function post_record(record) { + record = JSON.stringify(record); + return fetch('/post_record', + { + method: 'POST', + body: record + }); + }, + + //model: a Vue object + //GET top 10 of a level and assign it to model.top10 + get_top10: function get_top10(levelIndex) { + let self = this; + fetch('/get_top10/'+levelIndex, {method: 'GET'}).then(function(response) { + response.json().then(function(data) { + console.log(self); + self.top10_list_vue.top10 = data; + }) + }); + } + }; +})(); diff --git a/example/game/readme.md b/example/game/readme.md new file mode 100644 index 0000000..b45b99e --- /dev/null +++ b/example/game/readme.md @@ -0,0 +1,17 @@ +# Element Master Game Example +This example is a HTML5 game I created. The game is written in VueJS and is served by Hyper2Web. + +# Backend functionality demonstrated by this game +0. GET + + Needless to say. GET has to work in order to load the page. +1. POST + + After the user beats a level, the front end sends(post) a time record to the backend. The backend keeps track of the best plays. +2. Server Push + + This HTTP/2 new feature is not supported by the framework yet. + + When it is supported, the user will get live update of top10 plays of the current level. + + In the old days of HTTP/1. One can only implements this update with WebSocket or constantly GET requests. diff --git a/hyper2web/app.py b/hyper2web/app.py index f82ea1b..bb8775e 100644 --- a/hyper2web/app.py +++ b/hyper2web/app.py @@ -29,7 +29,6 @@ def __init__(self, port=5000, root='./public', if auto_serve_static_file: async def default_get(http, stream, parameters): - print('default_get') route = stream.headers[':path'].lstrip('/') full_path = os.path.join(self.root, route) if os.path.exists(full_path): @@ -52,7 +51,8 @@ def up(self): kernel.run(h2_server(address=("localhost", self.port), certfile="{}.crt.pem".format("localhost"), keyfile="{}.key".format("localhost"), - app=self)) + app=self), + shutdown=True) def get(self, route: str, handler): self._router.register('GET', route, handler) diff --git a/hyper2web/http.py b/hyper2web/http.py index 9e55c04..0d75e32 100644 --- a/hyper2web/http.py +++ b/hyper2web/http.py @@ -123,7 +123,6 @@ async def data_received(self, event: events.DataReceived): """ if event.stream_id not in self.streams: # But I think this situation is impossible since header should always arrive before data - print('data before header') raise Exception('data before header') # update this handler @@ -161,7 +160,6 @@ async def send_and_end(self, stream: Stream, data: bytes): """Send data associate with this stream to client and end the stream""" # Headers content_type, content_encoding = mimetypes.guess_type(str(data, encoding='utf8')) - print(content_type, content_encoding) response_headers = [ (':status', '200'), ('content-length', str(len(data))), diff --git a/hyper2web/router.py b/hyper2web/router.py index 2c465d1..3f50eaa 100644 --- a/hyper2web/router.py +++ b/hyper2web/router.py @@ -1,5 +1,3 @@ -import os - from .abstract import AbstractRouter from .http import HTTP, Stream @@ -43,7 +41,7 @@ def _match(cls, route, path): # '/something/xxx/' to 'something/xxx'. Get rid of '/' at the left and the right end of a string route = route.lstrip('/').rstrip('/').split('/') path = path.lstrip('/').rstrip('/').split('/') - print(route, path) + if len(route) != len(path): return False, None else: @@ -58,13 +56,10 @@ def _match(cls, route, path): parameters[r[1:-1]] = p elif r != p: return False, None - print('out of for loop') return True, parameters # async async def handle_route(self, http: HTTP, stream: Stream): - print('app.App.handle_route') - path = stream.headers[':path'] method = stream.headers[':method'] @@ -73,7 +68,6 @@ async def handle_route(self, http: HTTP, stream: Stream): # 如果没有任何匹配,就默认为静态文件读取 if route is None: if method == 'GET': - print('GET') handler = self.default_get else: handler = None @@ -81,8 +75,6 @@ async def handle_route(self, http: HTTP, stream: Stream): handler = self._routes[method].get(route, None) if handler is not None: - print('handle') - print(handler) await handler(http, stream, parameters) else: # maybe raise an error? diff --git a/hyper2web/sslsocket.py b/hyper2web/sslsocket.py index c53a03d..7059b08 100644 --- a/hyper2web/sslsocket.py +++ b/hyper2web/sslsocket.py @@ -1,3 +1,5 @@ +import os + from curio import socket, ssl @@ -5,18 +7,22 @@ def create_listening_ssl_socket(address, certfile, keyfile): """ Create and return a listening TLS socket on a given address. """ - ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) - ssl_context.options |= ( - ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_COMPRESSION - ) - ssl_context.set_ciphers("ECDHE+AESGCM") - ssl_context.load_cert_chain(certfile=certfile, keyfile=keyfile) - ssl_context.set_alpn_protocols(["h2"]) + # check if 2 files exist. If not, raise exceptions + if os.path.isfile(certfile) and os.path.isfile(keyfile): + ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + ssl_context.options |= ( + ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_COMPRESSION + ) + ssl_context.set_ciphers("ECDHE+AESGCM") + ssl_context.load_cert_chain(certfile=certfile, keyfile=keyfile) + ssl_context.set_alpn_protocols(["h2"]) - sock = socket.socket() - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - sock = ssl_context.wrap_socket(sock) - sock.bind(address) - sock.listen() + sock = socket.socket() + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock = ssl_context.wrap_socket(sock) + sock.bind(address) + sock.listen() - return sock + return sock + else: + raise FileNotFoundError(certfile + " and/or " + keyfile + " don't exist. HTTP/2 needs certificate files.")