blob: ed9d203681250f1f318a10080aeee8c35b9be9d2 [file] [log] [blame]
Dave Borowitz09ff62b2012-12-17 14:09:16 -08001// Copyright 2012 Google Inc. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package com.google.gitiles.dev;
16
Dave Borowitzc410f962014-09-23 10:49:26 -070017import static com.google.common.base.MoreObjects.firstNonNull;
Dave Borowitz03397692012-12-18 17:11:16 -080018import static com.google.gitiles.GitilesServlet.STATIC_PREFIX;
19
Shawn Pearce68311c72015-06-09 17:01:34 -070020import com.google.common.base.Strings;
Dave Borowitz03397692012-12-18 17:11:16 -080021import com.google.gitiles.DebugRenderer;
Shawn Pearce68311c72015-06-09 17:01:34 -070022import com.google.gitiles.GitilesAccess;
Dave Borowitz09ff62b2012-12-17 14:09:16 -080023import com.google.gitiles.GitilesServlet;
Dave Borowitz03397692012-12-18 17:11:16 -080024import com.google.gitiles.PathServlet;
Shawn Pearce68311c72015-06-09 17:01:34 -070025import com.google.gitiles.RepositoryDescription;
26import com.google.gitiles.RootedDocServlet;
Dave Borowitz09ff62b2012-12-17 14:09:16 -080027
28import org.eclipse.jetty.server.Connector;
29import org.eclipse.jetty.server.Handler;
30import org.eclipse.jetty.server.Server;
Dave Borowitze1056bb2013-01-11 10:31:37 -080031import org.eclipse.jetty.server.bio.SocketConnector;
Dave Borowitz09ff62b2012-12-17 14:09:16 -080032import org.eclipse.jetty.server.handler.ContextHandler;
33import org.eclipse.jetty.server.handler.ContextHandlerCollection;
34import org.eclipse.jetty.server.handler.ResourceHandler;
Dave Borowitz09ff62b2012-12-17 14:09:16 -080035import org.eclipse.jetty.servlet.ServletContextHandler;
36import org.eclipse.jetty.servlet.ServletHolder;
37import org.eclipse.jetty.util.resource.FileResource;
38import org.eclipse.jetty.util.thread.QueuedThreadPool;
39import org.eclipse.jetty.util.thread.ThreadPool;
Dave Borowitz03397692012-12-18 17:11:16 -080040import org.eclipse.jgit.errors.ConfigInvalidException;
Shawn Pearce68311c72015-06-09 17:01:34 -070041import org.eclipse.jgit.errors.RepositoryNotFoundException;
Dave Borowitz09ff62b2012-12-17 14:09:16 -080042import org.eclipse.jgit.lib.Config;
Shawn Pearce68311c72015-06-09 17:01:34 -070043import org.eclipse.jgit.lib.Constants;
44import org.eclipse.jgit.lib.Repository;
45import org.eclipse.jgit.lib.RepositoryCache;
46import org.eclipse.jgit.lib.RepositoryCache.FileKey;
Dave Borowitz03397692012-12-18 17:11:16 -080047import org.eclipse.jgit.storage.file.FileBasedConfig;
Shawn Pearce68311c72015-06-09 17:01:34 -070048import org.eclipse.jgit.transport.resolver.RepositoryResolver;
Dave Borowitz03397692012-12-18 17:11:16 -080049import org.eclipse.jgit.util.FS;
50import org.slf4j.Logger;
51import org.slf4j.LoggerFactory;
Dave Borowitz09ff62b2012-12-17 14:09:16 -080052
53import java.io.File;
54import java.io.FileNotFoundException;
55import java.io.IOException;
Dave Borowitz03397692012-12-18 17:11:16 -080056import java.net.InetAddress;
Dave Borowitz09ff62b2012-12-17 14:09:16 -080057import java.net.URI;
58import java.net.URISyntaxException;
Dave Borowitz03397692012-12-18 17:11:16 -080059import java.net.UnknownHostException;
Dave Borowitz76bbefd2014-03-11 16:57:45 -070060import java.util.Arrays;
Shawn Pearce68311c72015-06-09 17:01:34 -070061import java.util.Collections;
62import java.util.Map;
63import java.util.Set;
64
65import javax.servlet.Servlet;
66import javax.servlet.http.HttpServletRequest;
Dave Borowitz09ff62b2012-12-17 14:09:16 -080067
68class DevServer {
Dave Borowitz03397692012-12-18 17:11:16 -080069 private static final Logger log = LoggerFactory.getLogger(PathServlet.class);
70
Dave Borowitz823724a2013-07-30 13:18:42 -070071 private static Config defaultConfig() {
Dave Borowitz03397692012-12-18 17:11:16 -080072 Config cfg = new Config();
73 String cwd = System.getProperty("user.dir");
74 cfg.setString("gitiles", null, "basePath", cwd);
75 cfg.setBoolean("gitiles", null, "exportAll", true);
76 cfg.setString("gitiles", null, "baseGitUrl", "file://" + cwd + "/");
Dave Borowitz823724a2013-07-30 13:18:42 -070077 String networkHostName;
78 try {
79 networkHostName = InetAddress.getLocalHost().getCanonicalHostName();
80 } catch (UnknownHostException e) {
81 networkHostName = "127.0.0.1";
82 }
Dave Borowitz03397692012-12-18 17:11:16 -080083 cfg.setString("gitiles", null, "siteTitle",
84 String.format("Gitiles - %s:%s", networkHostName, cwd));
85 cfg.setString("gitiles", null, "canonicalHostName", new File(cwd).getName());
86 return cfg;
87 }
88
89 private static FileNotFoundException badSourceRoot(URI u) {
90 return new FileNotFoundException("Cannot find source root from " + u);
91 }
92
93 private static FileNotFoundException badSourceRoot(URI u, Throwable cause) {
94 FileNotFoundException notFound = badSourceRoot(u);
95 notFound.initCause(cause);
96 return notFound;
97 }
98
99 private static File findSourceRoot() throws IOException {
100 URI u;
101 try {
102 u = DevServer.class.getResource(DevServer.class.getSimpleName() + ".class").toURI();
103 } catch (URISyntaxException e) {
104 u = null;
105 }
106 if (u == null) {
107 throw new FileNotFoundException("Cannot find Gitiles source directory");
108 }
109 if ("jar".equals(u.getScheme())) {
Dave Borowitzb2f4d562012-12-27 16:36:37 -0800110 String path = u.getSchemeSpecificPart();
111 int jarEntry = path.indexOf("!/");
Dave Borowitz03397692012-12-18 17:11:16 -0800112 if (jarEntry < 0) {
113 throw badSourceRoot(u);
114 }
115 try {
Dave Borowitzb2f4d562012-12-27 16:36:37 -0800116 return findSourceRoot(new URI(path.substring(0, jarEntry)));
Dave Borowitz03397692012-12-18 17:11:16 -0800117 } catch (URISyntaxException e) {
118 throw badSourceRoot(u, e);
119 }
120 } else {
121 return findSourceRoot(u);
122 }
123 }
124
125 private static File findSourceRoot(URI targetUri) throws IOException {
126 if (!"file".equals(targetUri.getScheme())) {
127 throw badSourceRoot(targetUri);
128 }
129 String targetPath = targetUri.getPath();
David Ostrovsky22c45b32014-02-23 22:22:26 +0100130 // targetPath is an arbitrary path under buck-out/ in our Buck package
131 // layout.
132 int targetIndex = targetPath.lastIndexOf("buck-out/");
Dave Borowitz03397692012-12-18 17:11:16 -0800133 if (targetIndex < 0) {
134 throw badSourceRoot(targetUri);
135 }
136 String path = targetPath.substring(0, targetIndex);
137 URI u;
138 try {
139 u = new URI("file", path, null).normalize();
140 } catch (URISyntaxException e) {
141 throw new IOException(e);
142 }
143 File root = new File(u);
144 if (!root.exists() || !root.isDirectory()) {
145 throw badSourceRoot(targetUri);
146 }
147 return root;
148 }
149
150 private final File sourceRoot;
151 private final Config cfg;
Dave Borowitz09ff62b2012-12-17 14:09:16 -0800152 private final Server httpd;
153
Dave Borowitz03397692012-12-18 17:11:16 -0800154 DevServer(File cfgFile) throws IOException, ConfigInvalidException {
155 sourceRoot = findSourceRoot();
156
157 Config cfg = defaultConfig();
158 if (cfgFile.exists() && cfgFile.isFile()) {
159 FileBasedConfig fcfg = new FileBasedConfig(cfg, cfgFile, FS.DETECTED);
160 fcfg.load();
161 cfg = fcfg;
162 } else {
Dave Borowitzfd25c3a2013-01-11 14:37:11 -0800163 log.info("Config file {} not found, using defaults", cfgFile.getPath());
Dave Borowitz03397692012-12-18 17:11:16 -0800164 }
165 this.cfg = cfg;
166
Dave Borowitz09ff62b2012-12-17 14:09:16 -0800167 httpd = new Server();
Dave Borowitz03397692012-12-18 17:11:16 -0800168 httpd.setConnectors(connectors());
169 httpd.setThreadPool(threadPool());
Dave Borowitz09ff62b2012-12-17 14:09:16 -0800170 httpd.setHandler(handler());
171 }
172
173 void start() throws Exception {
174 httpd.start();
175 httpd.join();
176 }
177
Dave Borowitz03397692012-12-18 17:11:16 -0800178 private Connector[] connectors() {
Dave Borowitze1056bb2013-01-11 10:31:37 -0800179 Connector c = new SocketConnector();
Dave Borowitz09ff62b2012-12-17 14:09:16 -0800180 c.setHost(null);
181 c.setPort(cfg.getInt("gitiles", null, "port", 8080));
182 c.setStatsOn(false);
183 return new Connector[]{c};
184 }
185
Dave Borowitz03397692012-12-18 17:11:16 -0800186 private ThreadPool threadPool() {
Dave Borowitz09ff62b2012-12-17 14:09:16 -0800187 QueuedThreadPool pool = new QueuedThreadPool();
188 pool.setName("HTTP");
189 pool.setMinThreads(2);
190 pool.setMaxThreads(10);
191 pool.setMaxQueued(50);
192 return pool;
193 }
194
195 private Handler handler() throws IOException {
196 ContextHandlerCollection handlers = new ContextHandlerCollection();
197 handlers.addHandler(staticHandler());
198 handlers.addHandler(appHandler());
199 return handlers;
Dave Borowitz09ff62b2012-12-17 14:09:16 -0800200 }
201
202 private Handler appHandler() {
Shawn Pearce68311c72015-06-09 17:01:34 -0700203 DebugRenderer renderer = new DebugRenderer(
204 STATIC_PREFIX,
205 Arrays.asList(cfg.getStringList("gitiles", null, "customTemplates")),
206 new File(sourceRoot, "gitiles-servlet/src/main/resources/com/google/gitiles/templates")
207 .getPath(),
208 firstNonNull(cfg.getString("gitiles", null, "siteTitle"), "Gitiles"));
209
210 String docRoot = cfg.getString("gitiles", null, "docroot");
211 Servlet servlet;
212 if (!Strings.isNullOrEmpty(docRoot)) {
213 servlet = createRootedDocServlet(renderer, docRoot);
214 } else {
215 servlet = new GitilesServlet(
216 cfg,
217 renderer,
218 null, null, null, null, null, null, null);
219 }
Dave Borowitz03397692012-12-18 17:11:16 -0800220
Dave Borowitz09ff62b2012-12-17 14:09:16 -0800221 ServletContextHandler handler = new ServletContextHandler();
222 handler.setContextPath("");
Dave Borowitz03397692012-12-18 17:11:16 -0800223 handler.addServlet(new ServletHolder(servlet), "/*");
Dave Borowitz09ff62b2012-12-17 14:09:16 -0800224 return handler;
225 }
226
Dave Borowitz03397692012-12-18 17:11:16 -0800227 private Handler staticHandler() throws IOException {
228 File staticRoot = new File(sourceRoot,
229 "gitiles-servlet/src/main/resources/com/google/gitiles/static");
Dave Borowitz09ff62b2012-12-17 14:09:16 -0800230 ResourceHandler rh = new ResourceHandler();
231 try {
Dave Borowitz03397692012-12-18 17:11:16 -0800232 rh.setBaseResource(new FileResource(staticRoot.toURI().toURL()));
Dave Borowitz09ff62b2012-12-17 14:09:16 -0800233 } catch (URISyntaxException e) {
Dave Borowitz03397692012-12-18 17:11:16 -0800234 throw new IOException(e);
Dave Borowitz09ff62b2012-12-17 14:09:16 -0800235 }
236 rh.setWelcomeFiles(new String[]{});
237 rh.setDirectoriesListed(false);
238 ContextHandler handler = new ContextHandler("/+static");
239 handler.setHandler(rh);
240 return handler;
241 }
Shawn Pearce68311c72015-06-09 17:01:34 -0700242
243 private Servlet createRootedDocServlet(DebugRenderer renderer, String docRoot) {
244 File docRepo = new File(docRoot);
245 final FileKey repoKey = FileKey.exact(docRepo, FS.DETECTED);
246
247 RepositoryResolver<HttpServletRequest> resolver = new RepositoryResolver<HttpServletRequest>() {
248 @Override
249 public Repository open(HttpServletRequest req, String name)
250 throws RepositoryNotFoundException {
251 try {
252 return RepositoryCache.open(repoKey, true);
253 } catch (IOException e) {
254 throw new RepositoryNotFoundException(repoKey.getFile(), e);
255 }
256 }
257 };
258
259 return new RootedDocServlet(
260 resolver,
261 new RootedDocAccess(docRepo),
262 renderer);
263 }
264
265 private class RootedDocAccess implements GitilesAccess.Factory {
266 private final String repoName;
267
268 RootedDocAccess(File docRepo) {
269 if (Constants.DOT_GIT.equals(docRepo.getName())) {
270 repoName = docRepo.getParentFile().getName();
271 } else {
272 repoName = docRepo.getName();
273 }
274 }
275
276 @Override
277 public GitilesAccess forRequest(HttpServletRequest req) {
278 return new GitilesAccess() {
279 @Override
Shawn Pearcec709c4c2015-08-28 15:30:42 -0700280 public Map<String, RepositoryDescription> listRepositories(
281 String prefix, Set<String> branches) {
Shawn Pearce68311c72015-06-09 17:01:34 -0700282 return Collections.emptyMap();
283 }
284
285 @Override
286 public Object getUserKey() {
287 return null;
288 }
289
290 @Override
291 public String getRepositoryName() {
292 return repoName;
293 }
294
295 @Override
296 public RepositoryDescription getRepositoryDescription() {
297 RepositoryDescription d = new RepositoryDescription();
298 d.name = getRepositoryName();
299 return d;
300 }
301
302 @Override
303 public Config getConfig() {
304 return cfg;
305 }
306 };
307 }
308 }
309
Dave Borowitz09ff62b2012-12-17 14:09:16 -0800310}