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."""
27 from google
.appengine
.api
import appinfo
28 from google
.appengine
.tools
.devappserver2
import application_configuration
29 from google
.appengine
.tools
.devappserver2
import go_application
30 from google
.appengine
.tools
.devappserver2
import http_runtime
31 from google
.appengine
.tools
.devappserver2
import instance
33 _REBUILD_CONFIG_CHANGES
= frozenset(
34 [application_configuration
.SKIP_FILES_CHANGED
,
35 application_configuration
.NOBUILD_FILES_CHANGED
])
38 class _GoBuildFailureRuntimeProxy(instance
.RuntimeProxy
):
39 """Serves an error page for a Go application build failure."""
41 def __init__(self
, failure_exception
):
42 self
._failure
_exception
= failure_exception
50 def handle(self
, environ
, start_response
, url_map
, match
, request_id
,
52 """Serves a request by displaying an error page.
55 environ: An environ dict for the request as defined in PEP-333.
56 start_response: A function with semantics defined in PEP-333.
57 url_map: An appinfo.URLMap instance containing the configuration for the
58 handler matching this request.
59 match: A re.MatchObject containing the result of the matched URL pattern.
60 request_id: A unique string id associated with the request.
61 request_type: The type of the request. See instance.*_REQUEST module
65 A sequence of strings containing the body of the HTTP response.
67 start_response('500 Internal Server Error',
68 [('Content-Type', 'text/plain; charset=utf-8')])
69 yield 'The Go application could not be built.\n'
71 yield str(self
._failure
_exception
)
74 class GoRuntimeInstanceFactory(instance
.InstanceFactory
):
75 """A factory that creates new Go runtime Instances."""
77 START_URL_MAP
= appinfo
.URLMap(
81 WARMUP_URL_MAP
= appinfo
.URLMap(
85 FILE_CHANGE_INSTANCE_RESTART_POLICY
= instance
.ALWAYS
87 def __init__(self
, request_data
, runtime_config_getter
, module_configuration
):
88 """Initializer for GoRuntimeInstanceFactory.
91 request_data: A wsgi_request_info.WSGIRequestInfo that will be provided
92 with request information for use by API stubs.
93 runtime_config_getter: A function that can be called without arguments
94 and returns the runtime_config_pb2.RuntimeConfig containing the
95 configuration for the runtime.
96 module_configuration: An application_configuration.ModuleConfiguration
97 instance respresenting the configuration of the module that owns the
100 super(GoRuntimeInstanceFactory
, self
).__init
__(request_data
, 1)
101 self
._runtime
_config
_getter
= runtime_config_getter
102 self
._module
_configuration
= module_configuration
103 self
._application
_lock
= threading
.Lock()
104 self
._go
_application
= go_application
.GoApplication(
105 self
._module
_configuration
)
106 self
._modified
_since
_last
_build
= False
107 self
._last
_build
_error
= None
109 def get_restart_directories(self
):
110 """Returns a list of directories changes in which should trigger a restart.
113 A list of src directory paths in the GOPATH. Changes (i.e. files added,
114 deleted or modified) in these directories will trigger a restart of all
115 instances created with this factory.
118 go_path
= os
.environ
['GOPATH']
122 if sys
.platform
.startswith('win32'):
123 roots
= go_path
.split(';')
125 roots
= go_path
.split(':')
126 dirs
= [os
.path
.join(r
, 'src') for r
in roots
]
127 return [d
for d
in dirs
if os
.path
.isdir(d
)]
129 def files_changed(self
):
130 """Called when a file relevant to the factory *might* have changed."""
131 with self
._application
_lock
:
132 self
._modified
_since
_last
_build
= True
134 def configuration_changed(self
, config_changes
):
135 """Called when the configuration of the module has changed.
138 config_changes: A set containing the changes that occured. See the
139 *_CHANGED constants in the application_configuration module.
141 if config_changes
& _REBUILD_CONFIG_CHANGES
:
142 with self
._application
_lock
:
143 self
._modified
_since
_last
_build
= True
145 def new_instance(self
, instance_id
, expect_ready_request
=False):
146 """Create and return a new Instance.
149 instance_id: A string or integer representing the unique (per module) id
151 expect_ready_request: If True then the instance will be sent a special
152 request (i.e. /_ah/warmup or /_ah/start) before it can handle external
156 The newly created instance.Instance.
159 def instance_config_getter():
160 runtime_config
= self
._runtime
_config
_getter
()
161 runtime_config
.instance_id
= str(instance_id
)
162 return runtime_config
164 with self
._application
_lock
:
166 if self
._go
_application
.maybe_build(self
._modified
_since
_last
_build
):
167 if self
._last
_build
_error
:
168 logging
.info('Go application successfully built.')
169 self
._last
_build
_error
= None
170 except go_application
.BuildError
as e
:
171 logging
.error('Failed to build Go application: %s', e
)
172 # Deploy a failure proxy now and each time a new instance is requested.
173 self
._last
_build
_error
= e
175 self
._modified
_since
_last
_build
= False
177 if self
._last
_build
_error
:
178 logging
.debug('Deploying new instance of failure proxy.')
179 proxy
= _GoBuildFailureRuntimeProxy(self
._last
_build
_error
)
181 proxy
= http_runtime
.HttpRuntimeProxy(
182 self
._go
_application
.go_executable
,
183 instance_config_getter
,
184 self
._module
_configuration
,
185 self
._go
_application
.get_environment())
187 return instance
.Instance(self
.request_data
,
190 self
.max_concurrent_requests
,
191 self
.max_background_threads
,
192 expect_ready_request
)