Skip to content

Commit 60e5096

Browse files
committed
use some app-engine-magic code for the development appserver
1 parent c12d3a5 commit 60e5096

File tree

3 files changed

+382
-0
lines changed

3 files changed

+382
-0
lines changed

dev/aem/core_local.clj

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
; modified from https://github.com/gcv/appengine-magic under MIT License
2+
;
3+
; Copyright (c) 2010 Constantine Vetoshev
4+
;
5+
;Permission is hereby granted, free of charge, to any person obtaining a copy of
6+
;this software and associated documentation files (the "Software"), to deal in
7+
;the Software without restriction, including without limitation the rights to
8+
;use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9+
;the Software, and to permit persons to whom the Software is furnished to do so,
10+
;subject to the following conditions:
11+
;
12+
;The above copyright notice and this permission notice shall be included in all
13+
;copies or substantial portions of the Software.
14+
;
15+
;THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
;IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17+
;FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18+
;COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19+
;IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20+
;CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21+
22+
23+
(in-ns 'appengine-magic.core)
24+
25+
(use 'appengine-magic.local-env-helpers
26+
'[appengine-magic.servlet :only [servlet]]
27+
'[appengine-magic.swank :only [wrap-swank]]
28+
'[ring.middleware.file :only [wrap-file]]
29+
'[ring.middleware.file-info :only [wrap-file-info]])
30+
31+
(require '[clojure.string :as str]
32+
'[appengine-magic.jetty :as jetty]
33+
'[appengine-magic.blobstore-upload :as blobstore-upload])
34+
35+
(import java.io.File
36+
com.google.apphosting.api.ApiProxy)
37+
38+
39+
40+
;;; ----------------------------------------------------------------------------
41+
;;; appengine-magic core API functions
42+
;;; ----------------------------------------------------------------------------
43+
44+
(defn default-war-root []
45+
(-> (clojure.lang.RT/baseLoader)
46+
(.getResource ".")
47+
.getFile
48+
java.net.URLDecoder/decode
49+
(File. "../war")
50+
.getAbsolutePath))
51+
52+
53+
(defn appengine-base-url [& {:keys [https?] :or {https? false}}]
54+
;; NB: The https? argument is intentionally ignored. HTTPS is not supported
55+
;; for local environments.
56+
(str "http://localhost:"
57+
(str @appengine-magic.local-env-helpers/*current-server-port*)))
58+
59+
60+
(defn wrap-war-static [app, #^String war-root]
61+
(fn [req]
62+
(let [#^String uri (:uri req)]
63+
(if (.startsWith uri "/WEB-INF")
64+
(app req)
65+
((wrap-file-info (wrap-file app war-root)) req)))))
66+
67+
68+
(defmacro def-appengine-app [app-var-name handler & {:keys [war-root]}]
69+
`(def ~app-var-name
70+
(let [handler# ~handler
71+
war-root-arg# ~war-root
72+
war-root# (if (nil? war-root-arg#)
73+
(default-war-root)
74+
war-root-arg#)]
75+
{:handler (-> handler#
76+
wrap-swank
77+
(wrap-war-static war-root#))
78+
:war-root war-root#})))
79+
80+
81+
(defn make-appengine-request-environment-filter []
82+
(reify javax.servlet.Filter
83+
(init [_ filter-config]
84+
(.setAttribute (.getServletContext filter-config)
85+
"com.google.appengine.devappserver.ApiProxyLocal"
86+
(ApiProxy/getDelegate)))
87+
(destroy [_])
88+
(doFilter [_ req resp chain]
89+
(let [all-cookies (.getCookies req)
90+
login-cookie (when all-cookies
91+
(let [raw (first (filter #(= "dev_appserver_login" (.getName %))
92+
(.getCookies req)))]
93+
(when raw (.getValue raw))))
94+
[user-email user-admin? _] (when login-cookie
95+
(str/split login-cookie #":"))
96+
thread-environment-proxy (make-thread-environment-proxy :user-email user-email
97+
:user-admin? user-admin?)]
98+
(ApiProxy/setEnvironmentForCurrentThread thread-environment-proxy))
99+
(.doFilter chain req resp))))
100+
101+
102+
103+
;;; ----------------------------------------------------------------------------
104+
;;; development server controls
105+
;;; ----------------------------------------------------------------------------
106+
107+
(defonce ^{:dynamic true} *server* (atom nil))
108+
109+
110+
(defn start [appengine-app & {:keys [port join? high-replication in-memory]
111+
:or {port 8080, join? false, high-replication false, in-memory false}}]
112+
(let [war-root (java.io.File. (:war-root appengine-app))
113+
handler-servlet (servlet (:handler appengine-app))]
114+
(appengine-init war-root port high-replication in-memory)
115+
(reset!
116+
*server*
117+
(jetty/start
118+
{"/*" [(make-appengine-request-environment-filter)
119+
(com.google.apphosting.utils.servlet.TransactionCleanupFilter.)
120+
(com.google.appengine.api.blobstore.dev.ServeBlobFilter.)]}
121+
{"/" handler-servlet
122+
;; These mappings are from webdefault.xml in appengine-local-runtime-*.jar.
123+
"/_ah/admin" (com.google.apphosting.utils.servlet.DatastoreViewerServlet.)
124+
"/_ah/admin/backends" (com.google.apphosting.utils.servlet.ServersServlet.)
125+
"/_ah/admin/capabilitiesstatus" (com.google.apphosting.utils.servlet.CapabilitiesStatusServlet.)
126+
"/_ah/admin/datastore" (com.google.apphosting.utils.servlet.DatastoreViewerServlet.)
127+
"/_ah/admin/inboundmail" (com.google.apphosting.utils.servlet.InboundMailServlet.)
128+
"/_ah/admin/search" (com.google.apphosting.utils.servlet.SearchServlet.)
129+
"/_ah/admin/taskqueue" (com.google.apphosting.utils.servlet.TaskQueueViewerServlet.)
130+
"/_ah/admin/xmpp" (com.google.apphosting.utils.servlet.XmppServlet.)
131+
"/_ah/adminConsole" (org.apache.jsp.ah.adminConsole_jsp.)
132+
"/_ah/backendsBody" (org.apache.jsp.ah.backendsBody_jsp.)
133+
"/_ah/backendsFinal" (org.apache.jsp.ah.backendsFinal_jsp.)
134+
"/_ah/backendsHead" (org.apache.jsp.ah.backendsHead_jsp.)
135+
"/_ah/blobImage" (com.google.appengine.api.images.dev.LocalBlobImageServlet.)
136+
"/_ah/blobUpload" (com.google.appengine.api.blobstore.dev.UploadBlobServlet.)
137+
"/_ah/capabilitiesStatusBody" (org.apache.jsp.ah.capabilitiesStatusBody_jsp.)
138+
"/_ah/capabilitiesStatusFinal" (org.apache.jsp.ah.capabilitiesStatusFinal_jsp.)
139+
"/_ah/capabilitiesStatusHead" (org.apache.jsp.ah.capabilitiesStatusHead_jsp.)
140+
"/_ah/capabilitiesViewer" (com.google.apphosting.utils.servlet.CapabilitiesStatusServlet.)
141+
"/_ah/channel/jsapi" (com.google.appengine.api.channel.dev.ServeScriptServlet.)
142+
"/_ah/channelLocalChannel" (com.google.appengine.api.channel.dev.LocalChannelServlet.)
143+
"/_ah/datastoreViewer" (com.google.apphosting.utils.servlet.DatastoreViewerServlet.)
144+
"/_ah/datastoreViewerBody" (org.apache.jsp.ah.datastoreViewerBody_jsp.)
145+
"/_ah/datastoreViewerFinal" (org.apache.jsp.ah.datastoreViewerFinal_jsp.)
146+
"/_ah/datastoreViewerHead" (org.apache.jsp.ah.datastoreViewerHead_jsp.)
147+
"/_ah/entityDetailsBody" (org.apache.jsp.ah.entityDetailsBody_jsp.)
148+
"/_ah/entityDetailsFinal" (org.apache.jsp.ah.entityDetailsFinal_jsp.)
149+
"/_ah/entityDetailsHead" (org.apache.jsp.ah.entityDetailsHead_jsp.)
150+
"/_ah/inboundmailBody" (org.apache.jsp.ah.inboundMailBody_jsp.)
151+
"/_ah/inboundmailFinal" (org.apache.jsp.ah.inboundMailFinal_jsp.)
152+
"/_ah/inboundmailHead" (org.apache.jsp.ah.inboundMailHead_jsp.)
153+
"/_ah/indexDetailsBody" (org.apache.jsp.ah.indexDetailsBody_jsp.)
154+
"/_ah/indexDetailsFinal" (org.apache.jsp.ah.indexDetailsFinal_jsp.)
155+
"/_ah/indexDetailsHead" (org.apache.jsp.ah.indexDetailsHead_jsp.)
156+
"/_ah/login" (com.google.appengine.api.users.dev.LocalLoginServlet.)
157+
"/_ah/logout" (com.google.appengine.api.users.dev.LocalLogoutServlet.)
158+
"/_ah/oauthAuthorizeToken" (com.google.appengine.api.users.dev.LocalOAuthAuthorizeTokenServlet.)
159+
"/_ah/oauthGetAccessToken" (com.google.appengine.api.users.dev.LocalOAuthAccessTokenServlet.)
160+
"/_ah/oauthGetRequestToken" (com.google.appengine.api.users.dev.LocalOAuthRequestTokenServlet.)
161+
"/_ah/queue_deferred" (com.google.apphosting.utils.servlet.DeferredTaskServlet.)
162+
"/_ah/resources" (com.google.apphosting.utils.servlet.AdminConsoleResourceServlet.)
163+
"/_ah/searchDocumentBody" (org.apache.jsp.ah.searchDocumentBody_jsp.)
164+
"/_ah/searchDocumentFinal" (org.apache.jsp.ah.searchDocumentFinal_jsp.)
165+
"/_ah/searchDocumentHead" (org.apache.jsp.ah.searchDocumentHead_jsp.)
166+
"/_ah/searchIndexBody" (org.apache.jsp.ah.searchIndexBody_jsp.)
167+
"/_ah/searchIndexFinal" (org.apache.jsp.ah.searchIndexFinal_jsp.)
168+
"/_ah/searchIndexHead" (org.apache.jsp.ah.searchIndexHead_jsp.)
169+
"/_ah/searchIndexesListBody" (org.apache.jsp.ah.searchIndexesListBody_jsp.)
170+
"/_ah/searchIndexesListFinal" (org.apache.jsp.ah.searchIndexesListFinal_jsp.)
171+
"/_ah/searchIndexesListHead" (org.apache.jsp.ah.searchIndexesListHead_jsp.)
172+
"/_ah/sessioncleanup" (com.google.apphosting.utils.servlet.SessionCleanupServlet.)
173+
"/_ah/taskqueueViewerBody" (org.apache.jsp.ah.taskqueueViewerBody_jsp.)
174+
"/_ah/taskqueueViewerFinal" (org.apache.jsp.ah.taskqueueViewerFinal_jsp.)
175+
"/_ah/taskqueueViewerHead" (org.apache.jsp.ah.taskqueueViewerHead_jsp.)
176+
"/_ah/upload/*" (servlet (blobstore-upload/make-blob-upload-handler war-root))
177+
"/_ah/xmppBody" (org.apache.jsp.ah.xmppBody_jsp.)
178+
"/_ah/xmppFinal" (org.apache.jsp.ah.xmppFinal_jsp.)
179+
"/_ah/xmppHead" (org.apache.jsp.ah.xmppHead_jsp.)}
180+
:port port
181+
:join? join?))))
182+
183+
184+
(defn stop []
185+
(when-not (nil? @*server*)
186+
(appengine-clear)
187+
(jetty/stop @*server*)
188+
(reset! *server* nil)))
189+
190+
191+
(defn serve [appengine-app & {:keys [port high-replication in-memory]
192+
:or {port 8080, high-replication false, in-memory false}}]
193+
(stop)
194+
(start appengine-app :port port :high-replication high-replication :in-memory in-memory))

dev/aem/jetty.clj

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
; modified from https://github.com/gcv/appengine-magic under MIT License
2+
;
3+
; Copyright (c) 2010 Constantine Vetoshev
4+
;
5+
;Permission is hereby granted, free of charge, to any person obtaining a copy of
6+
;this software and associated documentation files (the "Software"), to deal in
7+
;the Software without restriction, including without limitation the rights to
8+
;use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9+
;the Software, and to permit persons to whom the Software is furnished to do so,
10+
;subject to the following conditions:
11+
;
12+
;The above copyright notice and this permission notice shall be included in all
13+
;copies or substantial portions of the Software.
14+
;
15+
;THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
;IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17+
;FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18+
;COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19+
;IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20+
;CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21+
22+
23+
(ns appengine-magic.jetty
24+
(:use [appengine-magic.servlet :only [servlet]])
25+
(:import org.mortbay.jetty.handler.ContextHandlerCollection
26+
[org.mortbay.jetty Server Handler]
27+
javax.servlet.http.HttpServlet
28+
javax.servlet.Filter
29+
[org.mortbay.jetty.servlet Context ServletHolder FilterHolder]))
30+
31+
32+
(defn- proxy-multihandler
33+
"Returns a Jetty Handler implementation for the given map of relative URLs to
34+
handlers. Each handler may be a Ring handler or an HttpServlet instance."
35+
[filters all-handlers]
36+
(let [all-contexts (ContextHandlerCollection.)
37+
context (Context. all-contexts "/" Context/SESSIONS)]
38+
(doseq [[url filter-objs] filters]
39+
(let [filter-objs (if (sequential? filter-objs) filter-objs [filter-objs])]
40+
(doseq [filter-obj filter-objs]
41+
(.addFilter context (FilterHolder. filter-obj) url Handler/ALL))))
42+
(doseq [[relative-url url-handler] all-handlers]
43+
(.addServlet context (ServletHolder. url-handler) relative-url))
44+
all-contexts))
45+
46+
47+
(defn #^Server start [filter-map servlet-map &
48+
{:keys [port join?] :or {port 8080 join? false}}]
49+
(let [server (Server. port)]
50+
(doto server
51+
(.setHandler (proxy-multihandler filter-map servlet-map))
52+
(.start))
53+
(when join? (.join server))
54+
server))
55+
56+
57+
(defn stop [#^Server server]
58+
(.stop server))

dev/aem/servlet.clj

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
; modified from https://github.com/gcv/appengine-magic under MIT License
2+
;
3+
; Copyright (c) 2010 Constantine Vetoshev
4+
;
5+
;Permission is hereby granted, free of charge, to any person obtaining a copy of
6+
;this software and associated documentation files (the "Software"), to deal in
7+
;the Software without restriction, including without limitation the rights to
8+
;use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9+
;the Software, and to permit persons to whom the Software is furnished to do so,
10+
;subject to the following conditions:
11+
;
12+
;The above copyright notice and this permission notice shall be included in all
13+
;copies or substantial portions of the Software.
14+
;
15+
;THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
;IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17+
;FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18+
;COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19+
;IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20+
;CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21+
22+
23+
;;; This code is adapted from Ring (http://github.com/mmcgrana/ring).
24+
;;;
25+
;;; Required change from Ring: removed dependencies which use Java classes
26+
;;; blacklisted in App Engine.
27+
28+
29+
(ns appengine-magic.servlet
30+
(:use [appengine-magic.utils :only [copy-stream]])
31+
(:import [java.io File FileInputStream InputStream ByteArrayInputStream OutputStream]
32+
[javax.servlet.http HttpServlet HttpServletRequest HttpServletResponse]))
33+
34+
35+
(defn- get-headers [^HttpServletRequest request]
36+
(reduce (fn [headers, ^String name]
37+
(assoc headers (.toLowerCase name) (.getHeader request name)))
38+
{}
39+
(enumeration-seq (.getHeaderNames request))))
40+
41+
42+
(defn- make-request-map [^HttpServlet servlet
43+
^HttpServletRequest request
44+
^HttpServletResponse response]
45+
{:servlet servlet
46+
:response response
47+
:request request
48+
:servlet-context (.getServletContext servlet)
49+
:server-port (.getServerPort request)
50+
:server-name (.getServerName request)
51+
:remote-addr (.getRemoteAddr request)
52+
:uri (.getRequestURI request)
53+
:query-string (.getQueryString request)
54+
:scheme (keyword (.getScheme request))
55+
:request-method (keyword (.toLowerCase (.getMethod request)))
56+
:headers (get-headers request)
57+
:content-type (.getContentType request)
58+
:content-length (.getContentLength request)
59+
:character-encoding (.getCharacterEncoding request)
60+
:body (.getInputStream request)})
61+
62+
63+
(defn- set-response-headers [^HttpServletResponse response, headers]
64+
(doseq [[key val-or-vals] headers]
65+
(if (string? val-or-vals)
66+
(.setHeader response key val-or-vals)
67+
(doseq [val val-or-vals]
68+
(.addHeader response key val))))
69+
;; Use specific servlet API methods for some headers:
70+
(.setCharacterEncoding response "UTF-8")
71+
(when-let [content-type (get headers "Content-Type")]
72+
(.setContentType response content-type)))
73+
74+
75+
(defn- set-response-body [^HttpServletResponse response, body]
76+
(cond
77+
;; just a string
78+
(string? body)
79+
(with-open [writer (.getWriter response)]
80+
(.print writer body))
81+
;; any Clojure seq
82+
(seq? body)
83+
(with-open [writer (.getWriter response)]
84+
(doseq [chunk body]
85+
(.print writer (str chunk))
86+
(.flush writer)))
87+
;; a Java InputStream
88+
(instance? InputStream body)
89+
(with-open [out (.getOutputStream response)
90+
^InputStream b body]
91+
(copy-stream b out)
92+
(.flush out))
93+
;; serve up a File
94+
(instance? File body)
95+
(let [^File f body]
96+
(with-open [stream (FileInputStream. f)]
97+
(set-response-body response stream)))
98+
;; serve up a byte array
99+
(instance? (class (byte-array 0)) body)
100+
(with-open [in (ByteArrayInputStream. body)]
101+
(set-response-body response in))
102+
;; nothing
103+
(nil? body) nil
104+
;; unknown
105+
:else (throw (RuntimeException. (str "handler response body unknown" body)))))
106+
107+
108+
(defn- adapt-servlet-response [^HttpServletResponse response,
109+
{:keys [commit? status headers body]
110+
:or {commit? true}}]
111+
(when commit?
112+
(if status
113+
(.setStatus response status)
114+
(throw (RuntimeException. "handler response status not set")))
115+
(when headers (set-response-headers response headers))
116+
(when body (set-response-body response body))))
117+
118+
119+
(defn make-servlet-service-method [ring-handler]
120+
(fn [^HttpServlet servlet, ^HttpServletRequest request, ^HttpServletResponse response]
121+
(let [response-map (doall (ring-handler (make-request-map servlet request response)))]
122+
(when-not response-map
123+
(throw (RuntimeException. "handler returned nil (no response map)")))
124+
(adapt-servlet-response response response-map))))
125+
126+
127+
(defn servlet [ring-handler]
128+
(proxy [HttpServlet] []
129+
(service [^HttpServletRequest request, ^HttpServletResponse response]
130+
((make-servlet-service-method ring-handler) this request response))))

0 commit comments

Comments
 (0)