3 # Copyright 2007 Google Inc.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 """Serves content for "script" handlers using the Go runtime."""
26 from google
.appengine
.api
import appinfo
27 from google
.appengine
.tools
.devappserver2
import application_configuration
28 from google
.appengine
.tools
.devappserver2
import go_application
29 from google
.appengine
.tools
.devappserver2
import http_runtime
30 from google
.appengine
.tools
.devappserver2
import instance
32 _REBUILD_CONFIG_CHANGES
= frozenset(
33 [application_configuration
.SKIP_FILES_CHANGED
,
34 application_configuration
.NOBUILD_FILES_CHANGED
])
37 class _GoBuildFailureRuntimeProxy(instance
.RuntimeProxy
):
38 """Serves an error page for a Go application build failure."""
40 def __init__(self
, failure_exception
):
41 self
._failure
_exception
= failure_exception
49 def handle(self
, environ
, start_response
, url_map
, match
, request_id
,
51 """Serves a request by displaying an error page.
54 environ: An environ dict for the request as defined in PEP-333.
55 start_response: A function with semantics defined in PEP-333.
56 url_map: An appinfo.URLMap instance containing the configuration for the
57 handler matching this request.
58 match: A re.MatchObject containing the result of the matched URL pattern.
59 request_id: A unique string id associated with the request.
60 request_type: The type of the request. See instance.*_REQUEST module
64 A sequence of strings containing the body of the HTTP response.
66 start_response('500 Internal Server Error',
67 [('Content-Type', 'text/plain; charset=utf-8')])
68 yield 'The Go application could not be built.\n'
70 yield str(self
._failure
_exception
)
73 class GoRuntimeInstanceFactory(instance
.InstanceFactory
):
74 """A factory that creates new Go runtime Instances."""
76 START_URL_MAP
= appinfo
.URLMap(
80 WARMUP_URL_MAP
= appinfo
.URLMap(
84 FILE_CHANGE_INSTANCE_RESTART_POLICY
= instance
.ALWAYS
86 def __init__(self
, request_data
, runtime_config_getter
, module_configuration
):
87 """Initializer for GoRuntimeInstanceFactory.
90 request_data: A wsgi_request_info.WSGIRequestInfo that will be provided
91 with request information for use by API stubs.
92 runtime_config_getter: A function that can be called without arguments
93 and returns the runtime_config_pb2.RuntimeConfig containing the
94 configuration for the runtime.
95 module_configuration: An application_configuration.ModuleConfiguration
96 instance respresenting the configuration of the module that owns the
99 super(GoRuntimeInstanceFactory
, self
).__init
__(request_data
, 1)
100 self
._runtime
_config
_getter
= runtime_config_getter
101 self
._module
_configuration
= module_configuration
102 self
._application
_lock
= threading
.Lock()
103 self
._go
_application
= go_application
.GoApplication(
104 self
._module
_configuration
)
105 self
._modified
_since
_last
_build
= False
107 def get_restart_directories(self
):
108 """Returns a list of directories changes in which should trigger a restart.
111 A list of src directory paths in the GOPATH. Changes (i.e. files added,
112 deleted or modified) in these directories will trigger a restart of all
113 instances created with this factory.
116 go_path
= os
.environ
['GOPATH']
120 if sys
.platform
.startswith('win32'):
121 roots
= go_path
.split(';')
123 roots
= go_path
.split(':')
124 dirs
= [os
.path
.join(r
, 'src') for r
in roots
]
125 return [d
for d
in dirs
if os
.path
.isdir(d
)]
127 def files_changed(self
):
128 """Called when a file relevant to the factory *might* have changed."""
129 with self
._application
_lock
:
130 self
._modified
_since
_last
_build
= True
132 def configuration_changed(self
, config_changes
):
133 """Called when the configuration of the module has changed.
136 config_changes: A set containing the changes that occured. See the
137 *_CHANGED constants in the application_configuration module.
139 if config_changes
& _REBUILD_CONFIG_CHANGES
:
140 with self
._application
_lock
:
141 self
._modified
_since
_last
_build
= True
143 def new_instance(self
, instance_id
, expect_ready_request
=False):
144 """Create and return a new Instance.
147 instance_id: A string or integer representing the unique (per module) id
149 expect_ready_request: If True then the instance will be sent a special
150 request (i.e. /_ah/warmup or /_ah/start) before it can handle external
154 The newly created instance.Instance.
157 def instance_config_getter():
158 runtime_config
= self
._runtime
_config
_getter
()
159 runtime_config
.instance_id
= str(instance_id
)
160 return runtime_config
162 with self
._application
_lock
:
164 self
._go
_application
.maybe_build(self
._modified
_since
_last
_build
)
165 except go_application
.BuildError
as e
:
166 logging
.error('Failed to build Go application: %s', e
)
167 proxy
= _GoBuildFailureRuntimeProxy(e
)
169 proxy
= http_runtime
.HttpRuntimeProxy(
170 self
._go
_application
.go_executable
,
171 instance_config_getter
,
172 self
._module
_configuration
,
173 self
._go
_application
.get_environment())
174 self
._modified
_since
_last
_build
= False
176 return instance
.Instance(self
.request_data
,
179 self
.max_concurrent_requests
,
180 self
.max_background_threads
,
181 expect_ready_request
)