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

116 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-24 12:46 +0200

1"""web application root""" 

2 

3import gws 

4import gws.base.web 

5import gws.config 

6 

7_STATE = { 

8 'inited': False, 

9} 

10 

11 

12def application(environ, start_response): 

13 if not _STATE['inited']: 

14 init() 

15 root = gws.config.get_root() 

16 responder = handle_request(root, environ) 

17 return responder.send_response(environ, start_response) 

18 

19 

20def make_application(root): 

21 def fn(environ, start_response): 

22 responder = handle_request(root, environ) 

23 return responder.send_response(environ, start_response) 

24 

25 return fn 

26 

27 

28def init(): 

29 try: 

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

31 gws.log.set_level('DEBUG') 

32 root = gws.config.load() 

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

34 _STATE['inited'] = True 

35 except: 

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

37 gws.u.exit(1) 

38 

39 

40def reload(): 

41 _STATE['inited'] = False 

42 init() 

43 

44 

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

46 req = gws.base.web.wsgi.Requester(root, environ, root.app.webMgr.site) 

47 

48 try: 

49 req.parse() 

50 except Exception as exc: 

51 return handle_error(req, exc) 

52 

53 gws.log.if_debug(_debug_repr, f'REQUEST_BEGIN <{req.method}> {req.command()}', req.params() or req.struct()) 

54 gws.debug.time_start(f'REQUEST <{req.method}> {req.command()}') 

55 res = apply_middleware(root, req) 

56 gws.debug.time_end() 

57 gws.log.if_debug(_debug_repr, f'REQUEST_END <{req.method}> {req.command()}', res) 

58 

59 return res 

60 

61 

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

63 res = None 

64 done = [] 

65 

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

67 try: 

68 res = obj.enter_middleware(req) 

69 done.append(obj) 

70 except Exception as exc: 

71 res = handle_error(req, exc) 

72 

73 if res: 

74 break 

75 

76 if not res: 

77 try: 

78 m = req.method 

79 if m == gws.RequestMethod.GET or m == gws.RequestMethod.POST: 

80 res = handle_action(root, req) 

81 elif m == gws.RequestMethod.HEAD or m == gws.RequestMethod.OPTIONS: 

82 res = req.content_responder(gws.ContentResponse(mime='text/plain', content='')) 

83 else: 

84 raise gws.base.web.error.MethodNotAllowed(['GET', 'POST', 'HEAD', 'OPTIONS']) 

85 except Exception as exc: 

86 res = handle_error(req, exc) 

87 

88 for obj in reversed(done): 

89 try: 

90 obj.exit_middleware(req, res) 

91 except Exception as exc: 

92 res = handle_error(req, exc) 

93 

94 return res 

95 

96 

97def _debug_repr(prefix, s): 

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

99 m = 400 

100 n = len(s) 

101 if n <= m: 

102 return prefix + ': ' + s 

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

104 

105 

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

107 gws.log.if_debug(_debug_repr, f'REQUEST_ERROR', exc) 

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

109 return handle_http_error(req, web_exc) 

110 

111 

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

113 # 

114 # @TODO: image errors 

115 

116 if req.isApi: 

117 return req.api_responder( 

118 gws.Response( 

119 status=exc.code, 

120 error=gws.ResponseError( 

121 code=exc.code, 

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

123 ), 

124 ) 

125 ) 

126 

127 error_template = getattr(req.site, 'errorPage', None) 

128 if not error_template: 

129 error_template = req.root.app.templateMgr.find_template('application.error', where=[]) 

130 if not error_template: 

131 return req.error_responder(exc) 

132 

133 args = gws.TemplateArgs(req=req, user=req.user, error=exc.code, status=exc.code or 500) 

134 res = error_template.render(gws.TemplateRenderInput(args=args)) 

135 res.status = exc.code or 500 

136 return req.content_responder(res) 

137 

138 

139_relaxed_read_options = { 

140 gws.SpecReadOption.caseInsensitive, 

141 gws.SpecReadOption.convertValues, 

142 gws.SpecReadOption.ignoreExtraProps, 

143} 

144 

145 

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

147 if not req.command(): 

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

149 

150 if req.isApi: 

151 category = gws.CommandCategory.api 

152 params = req.struct() 

153 read_options = None 

154 elif req.isGet: 

155 category = gws.CommandCategory.get 

156 params = req.params() 

157 read_options = _relaxed_read_options 

158 elif req.isPost: 

159 category = gws.CommandCategory.post 

160 params = req.params() 

161 read_options = _relaxed_read_options 

162 else: 

163 # @TODO: add HEAD 

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

165 

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

167 category, 

168 req.command(), 

169 params, 

170 req.path(), 

171 req.user, 

172 read_options, 

173 ) 

174 

175 response = fn(req, request) 

176 

177 if response is None: 

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

179 

180 if isinstance(response, gws.ContentResponse): 

181 return req.content_responder(response) 

182 

183 if isinstance(response, gws.RedirectResponse): 

184 return req.redirect_responder(response) 

185 

186 return req.api_responder(response)