1#!/usr/bin/env python 2# 3# Copyright 2010 Google Inc. 4# 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 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 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. 16# 17 18"""Webapp forms interface to ProtoRPC services. 19 20This webapp application is automatically configured to work with ProtoRPCs 21that have a configured protorpc.RegistryService. This webapp is 22automatically added to the registry service URL at <registry-path>/forms 23(default is /protorpc/form) when configured using the 24service_handlers.service_mapping function. 25""" 26 27import os 28 29from .google_imports import template 30from .google_imports import webapp 31 32 33__all__ = ['FormsHandler', 34 'ResourceHandler', 35 36 'DEFAULT_REGISTRY_PATH', 37 ] 38 39_TEMPLATES_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), 40 'static') 41 42_FORMS_TEMPLATE = os.path.join(_TEMPLATES_DIR, 'forms.html') 43_METHODS_TEMPLATE = os.path.join(_TEMPLATES_DIR, 'methods.html') 44 45DEFAULT_REGISTRY_PATH = '/protorpc' 46 47 48class ResourceHandler(webapp.RequestHandler): 49 """Serves static resources without needing to add static files to app.yaml.""" 50 51 __RESOURCE_MAP = { 52 'forms.js': 'text/javascript', 53 'jquery-1.4.2.min.js': 'text/javascript', 54 'jquery.json-2.2.min.js': 'text/javascript', 55 } 56 57 def get(self, relative): 58 """Serve known static files. 59 60 If static file is not known, will return 404 to client. 61 62 Response items are cached for 300 seconds. 63 64 Args: 65 relative: Name of static file relative to main FormsHandler. 66 """ 67 content_type = self.__RESOURCE_MAP.get(relative, None) 68 if not content_type: 69 self.response.set_status(404) 70 self.response.out.write('Resource not found.') 71 return 72 73 path = os.path.join(_TEMPLATES_DIR, relative) 74 self.response.headers['Content-Type'] = content_type 75 static_file = open(path) 76 try: 77 contents = static_file.read() 78 finally: 79 static_file.close() 80 self.response.out.write(contents) 81 82 83class FormsHandler(webapp.RequestHandler): 84 """Handler for display HTML/javascript forms of ProtoRPC method calls. 85 86 When accessed with no query parameters, will show a web page that displays 87 all services and methods on the associated registry path. Links on this 88 page fill in the service_path and method_name query parameters back to this 89 same handler. 90 91 When provided with service_path and method_name parameters will display a 92 dynamic form representing the request message for that method. When sent, 93 the form sends a JSON request to the ProtoRPC method and displays the 94 response in the HTML page. 95 96 Attribute: 97 registry_path: Read-only registry path known by this handler. 98 """ 99 100 def __init__(self, registry_path=DEFAULT_REGISTRY_PATH): 101 """Constructor. 102 103 When configuring a FormsHandler to use with a webapp application do not 104 pass the request handler class in directly. Instead use new_factory to 105 ensure that the FormsHandler is created with the correct registry path 106 for each request. 107 108 Args: 109 registry_path: Absolute path on server where the ProtoRPC RegsitryService 110 is located. 111 """ 112 assert registry_path 113 self.__registry_path = registry_path 114 115 @property 116 def registry_path(self): 117 return self.__registry_path 118 119 def get(self): 120 """Send forms and method page to user. 121 122 By default, displays a web page listing all services and methods registered 123 on the server. Methods have links to display the actual method form. 124 125 If both parameters are set, will display form for method. 126 127 Query Parameters: 128 service_path: Path to service to display method of. Optional. 129 method_name: Name of method to display form for. Optional. 130 """ 131 params = {'forms_path': self.request.path.rstrip('/'), 132 'hostname': self.request.host, 133 'registry_path': self.__registry_path, 134 } 135 service_path = self.request.get('path', None) 136 method_name = self.request.get('method', None) 137 138 if service_path and method_name: 139 form_template = _METHODS_TEMPLATE 140 params['service_path'] = service_path 141 params['method_name'] = method_name 142 else: 143 form_template = _FORMS_TEMPLATE 144 145 self.response.out.write(template.render(form_template, params)) 146 147 @classmethod 148 def new_factory(cls, registry_path=DEFAULT_REGISTRY_PATH): 149 """Construct a factory for use with WSGIApplication. 150 151 This method is called automatically with the correct registry path when 152 services are configured via service_handlers.service_mapping. 153 154 Args: 155 registry_path: Absolute path on server where the ProtoRPC RegsitryService 156 is located. 157 158 Returns: 159 Factory function that creates a properly configured FormsHandler instance. 160 """ 161 def forms_factory(): 162 return cls(registry_path) 163 return forms_factory 164