Coverage for gws-app/gws/base/web/wsgi_app.py: 16%

111 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-17 01:37 +0200

1"""web application root""" 

2 

3from typing import cast 

4 

5import gws 

6import gws.base.action 

7import gws.base.web 

8import gws.config 

9import gws.spec.runtime 

10 

11_STATE = { 

12 'inited': False, 

13} 

14 

15 

16def application(environ, start_response): 

17 if not _STATE['inited']: 

18 init() 

19 root = gws.config.root() 

20 responder = handle_request(root, environ) 

21 return responder.send_response(environ, start_response) 

22 

23 

24def make_application(root): 

25 def fn(environ, start_response): 

26 responder = handle_request(root, environ) 

27 return responder.send_response(environ, start_response) 

28 

29 return fn 

30 

31 

32def init(): 

33 try: 

34 gws.log.info('initializing WEB application') 

35 gws.log.set_level('DEBUG') 

36 root = gws.config.load() 

37 gws.log.set_level(root.app.cfg('server.log.level')) 

38 _STATE['inited'] = True 

39 except: 

40 gws.log.exception('UNABLE TO LOAD CONFIGURATION') 

41 gws.u.exit(1) 

42 

43 

44def reload(): 

45 _STATE['inited'] = False 

46 init() 

47 

48 

49def handle_request(root: gws.Root, environ) -> gws.WebResponder: 

50 site = root.app.webMgr.site_from_environ(environ) 

51 req = gws.base.web.wsgi.Requester(root, environ, site) 

52 

53 try: 

54 _ = req.params() # enforce parsing 

55 except Exception as exc: 

56 return handle_error(req, exc) 

57 

58 gws.log.if_debug(_debug_repr, f'REQUEST_BEGIN {req.command()}', req.params() or req.struct()) 

59 gws.debug.time_start(f'REQUEST {req.command()}') 

60 res = apply_middleware(root, req) 

61 gws.debug.time_end() 

62 gws.log.if_debug(_debug_repr, f'REQUEST_END {req.command()}', res) 

63 

64 return res 

65 

66 

67def apply_middleware(root: gws.Root, req: gws.WebRequester) -> gws.WebResponder: 

68 res = None 

69 done = [] 

70 

71 for obj in root.app.middlewareMgr.objects(): 

72 try: 

73 res = obj.enter_middleware(req) 

74 done.append(obj) 

75 except Exception as exc: 

76 res = handle_error(req, exc) 

77 

78 if res: 

79 break 

80 

81 if not res: 

82 try: 

83 res = handle_action(root, req) 

84 except Exception as exc: 

85 res = handle_error(req, exc) 

86 

87 for obj in reversed(done): 

88 try: 

89 obj.exit_middleware(req, res) 

90 except Exception as exc: 

91 res = handle_error(req, exc) 

92 

93 return res 

94 

95 

96def _debug_repr(prefix, s): 

97 s = repr(gws.u.to_dict(s)) 

98 m = 400 

99 n = len(s) 

100 if n <= m: 

101 return prefix + ': ' + s 

102 return prefix + ': ' + s[:m] + ' [...' + str(n - m) + ' more]' 

103 

104 

105def handle_error(req: gws.WebRequester, exc: Exception) -> gws.WebResponder: 

106 web_exc = gws.base.web.error.from_exception(exc) 

107 return handle_http_error(req, web_exc) 

108 

109 

110def handle_http_error(req: gws.WebRequester, exc: gws.base.web.error.HTTPException) -> gws.WebResponder: 

111 # 

112 # @TODO: image errors 

113 

114 if req.isApi: 

115 return req.api_responder(gws.Response( 

116 status=exc.code, 

117 error=gws.ResponseError( 

118 code=exc.code, 

119 info=gws.u.get(exc, 'description', '')))) 

120 

121 if not req.site.errorPage: 

122 return req.error_responder(exc) 

123 

124 args = gws.TemplateArgs( 

125 req=req, 

126 user=req.user, 

127 error=exc.code 

128 ) 

129 res = req.site.errorPage.render(gws.TemplateRenderInput(args=args)) 

130 res.status = exc.code 

131 return req.content_responder(res) 

132 

133 

134_relaxed_read_options = { 

135 gws.SpecReadOption.caseInsensitive, 

136 gws.SpecReadOption.convertValues, 

137 gws.SpecReadOption.ignoreExtraProps, 

138} 

139 

140 

141def handle_action(root: gws.Root, req: gws.WebRequester) -> gws.WebResponder: 

142 if not req.command(): 

143 raise gws.NotFoundError('no command provided') 

144 

145 if req.isApi: 

146 category = gws.CommandCategory.api 

147 params = req.struct() 

148 read_options = None 

149 elif req.isGet: 

150 category = gws.CommandCategory.get 

151 params = req.params() 

152 read_options = _relaxed_read_options 

153 elif req.isPost: 

154 category = gws.CommandCategory.post 

155 params = req.params() 

156 read_options = _relaxed_read_options 

157 else: 

158 # @TODO: add HEAD 

159 raise gws.base.web.error.MethodNotAllowed() 

160 

161 fn, request = root.app.actionMgr.prepare_action( 

162 category, 

163 req.command(), 

164 params, 

165 req.user, 

166 read_options 

167 ) 

168 

169 response = fn(req, request) 

170 

171 if response is None: 

172 raise gws.NotFoundError(f'action not handled {category!r}:{req.command()!r}') 

173 

174 if isinstance(response, gws.ContentResponse): 

175 return req.content_responder(response) 

176 

177 if isinstance(response, gws.RedirectResponse): 

178 return req.redirect_responder(response) 

179 

180 return req.api_responder(response)