blob: f3f65972e8280f01b74f7a57987f0048f888219c [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.MoreObjects;
21import com.google.common.base.Strings;
Dave Borowitz03397692012-12-18 17:11:16 -080022import com.google.gitiles.DebugRenderer;
Shawn Pearce68311c72015-06-09 17:01:34 -070023import com.google.gitiles.GitilesAccess;
Dave Borowitz09ff62b2012-12-17 14:09:16 -080024import com.google.gitiles.GitilesServlet;
Dave Borowitz03397692012-12-18 17:11:16 -080025import com.google.gitiles.PathServlet;
Shawn Pearce68311c72015-06-09 17:01:34 -070026import com.google.gitiles.RepositoryDescription;
27import com.google.gitiles.RootedDocServlet;
Dave Borowitz09ff62b2012-12-17 14:09:16 -080028
29import org.eclipse.jetty.server.Connector;
30import org.eclipse.jetty.server.Handler;
31import org.eclipse.jetty.server.Server;
Dave Borowitze1056bb2013-01-11 10:31:37 -080032import org.eclipse.jetty.server.bio.SocketConnector;
Dave Borowitz09ff62b2012-12-17 14:09:16 -080033import org.eclipse.jetty.server.handler.ContextHandler;
34import org.eclipse.jetty.server.handler.ContextHandlerCollection;
35import org.eclipse.jetty.server.handler.ResourceHandler;
Dave Borowitz09ff62b2012-12-17 14:09:16 -080036import org.eclipse.jetty.servlet.ServletContextHandler;
37import org.eclipse.jetty.servlet.ServletHolder;
38import org.eclipse.jetty.util.resource.FileResource;
39import org.eclipse.jetty.util.thread.QueuedThreadPool;
40import org.eclipse.jetty.util.thread.ThreadPool;
Dave Borowitz03397692012-12-18 17:11:16 -080041import org.eclipse.jgit.errors.ConfigInvalidException;
Shawn Pearce68311c72015-06-09 17:01:34 -070042import org.eclipse.jgit.errors.RepositoryNotFoundException;
Dave Borowitz09ff62b2012-12-17 14:09:16 -080043import org.eclipse.jgit.lib.Config;
Shawn Pearce68311c72015-06-09 17:01:34 -070044import org.eclipse.jgit.lib.Constants;
45import org.eclipse.jgit.lib.Repository;
46import org.eclipse.jgit.lib.RepositoryCache;
47import org.eclipse.jgit.lib.RepositoryCache.FileKey;
Dave Borowitz03397692012-12-18 17:11:16 -080048import org.eclipse.jgit.storage.file.FileBasedConfig;
Shawn Pearce68311c72015-06-09 17:01:34 -070049import org.eclipse.jgit.transport.resolver.RepositoryResolver;
Dave Borowitz03397692012-12-18 17:11:16 -080050import org.eclipse.jgit.util.FS;
51import org.slf4j.Logger;
52import org.slf4j.LoggerFactory;
Dave Borowitz09ff62b2012-12-17 14:09:16 -080053
54import java.io.File;
55import java.io.FileNotFoundException;
56import java.io.IOException;
Dave Borowitz03397692012-12-18 17:11:16 -080057import java.net.InetAddress;
Dave Borowitz09ff62b2012-12-17 14:09:16 -080058import java.net.URI;
59import java.net.URISyntaxException;
Dave Borowitz03397692012-12-18 17:11:16 -080060import java.net.UnknownHostException;
Dave Borowitz76bbefd2014-03-11 16:57:45 -070061import java.util.Arrays;
Shawn Pearce68311c72015-06-09 17:01:34 -070062import java.util.Collections;
63import java.util.Map;
64import java.util.Set;
65
66import javax.servlet.Servlet;
67import javax.servlet.http.HttpServletRequest;
Dave Borowitz09ff62b2012-12-17 14:09:16 -080068
69class DevServer {
Dave Borowitz03397692012-12-18 17:11:16 -080070 private static final Logger log = LoggerFactory.getLogger(PathServlet.class);
71
Dave Borowitz823724a2013-07-30 13:18:42 -070072 private static Config defaultConfig() {
Dave Borowitz03397692012-12-18 17:11:16 -080073 Config cfg = new Config();
74 String cwd = System.getProperty("user.dir");
75 cfg.setString("gitiles", null, "basePath", cwd);
76 cfg.setBoolean("gitiles", null, "exportAll", true);
77 cfg.setString("gitiles", null, "baseGitUrl", "file://" + cwd + "/");
Dave Borowitz823724a2013-07-30 13:18:42 -070078 String networkHostName;
79 try {
80 networkHostName = InetAddress.getLocalHost().getCanonicalHostName();
81 } catch (UnknownHostException e) {
82 networkHostName = "127.0.0.1";
83 }
Dave Borowitz03397692012-12-18 17:11:16 -080084 cfg.setString("gitiles", null, "siteTitle",
85 String.format("Gitiles - %s:%s", networkHostName, cwd));
86 cfg.setString("gitiles", null, "canonicalHostName", new File(cwd).getName());
87 return cfg;
88 }
89
90 private static FileNotFoundException badSourceRoot(URI u) {
91 return new FileNotFoundException("Cannot find source root from " + u);
92 }
93
94 private static FileNotFoundException badSourceRoot(URI u, Throwable cause) {
95 FileNotFoundException notFound = badSourceRoot(u);
96 notFound.initCause(cause);
97 return notFound;
98 }
99
100 private static File findSourceRoot() throws IOException {
101 URI u;
102 try {
103 u = DevServer.class.getResource(DevServer.class.getSimpleName() + ".class").toURI();
104 } catch (URISyntaxException e) {
105 u = null;
106 }
107 if (u == null) {
108 throw new FileNotFoundException("Cannot find Gitiles source directory");
109 }
110 if ("jar".equals(u.getScheme())) {
Dave Borowitzb2f4d562012-12-27 16:36:37 -0800111 String path = u.getSchemeSpecificPart();
112 int jarEntry = path.indexOf("!/");
Dave Borowitz03397692012-12-18 17:11:16 -0800113 if (jarEntry < 0) {
114 throw badSourceRoot(u);
115 }
116 try {
Dave Borowitzb2f4d562012-12-27 16:36:37 -0800117 return findSourceRoot(new URI(path.substring(0, jarEntry)));
Dave Borowitz03397692012-12-18 17:11:16 -0800118 } catch (URISyntaxException e) {
119 throw badSourceRoot(u, e);
120 }
121 } else {
122 return findSourceRoot(u);
123 }
124 }
125
126 private static File findSourceRoot(URI targetUri) throws IOException {
127 if (!"file".equals(targetUri.getScheme())) {
128 throw badSourceRoot(targetUri);
129 }
130 String targetPath = targetUri.getPath();
David Ostrovsky22c45b32014-02-23 22:22:26 +0100131 // targetPath is an arbitrary path under buck-out/ in our Buck package
132 // layout.
133 int targetIndex = targetPath.lastIndexOf("buck-out/");
Dave Borowitz03397692012-12-18 17:11:16 -0800134 if (targetIndex < 0) {
135 throw badSourceRoot(targetUri);
136 }
137 String path = targetPath.substring(0, targetIndex);
138 URI u;
139 try {
140 u = new URI("file", path, null).normalize();
141 } catch (URISyntaxException e) {
142 throw new IOException(e);
143 }
144 File root = new File(u);
145 if (!root.exists() || !root.isDirectory()) {
146 throw badSourceRoot(targetUri);
147 }
148 return root;
149 }
150
151 private final File sourceRoot;
152 private final Config cfg;
Dave Borowitz09ff62b2012-12-17 14:09:16 -0800153 private final Server httpd;
154
Dave Borowitz03397692012-12-18 17:11:16 -0800155 DevServer(File cfgFile) throws IOException, ConfigInvalidException {
156 sourceRoot = findSourceRoot();
157
158 Config cfg = defaultConfig();
159 if (cfgFile.exists() && cfgFile.isFile()) {
160 FileBasedConfig fcfg = new FileBasedConfig(cfg, cfgFile, FS.DETECTED);
161 fcfg.load();
162 cfg = fcfg;
163 } else {
Dave Borowitzfd25c3a2013-01-11 14:37:11 -0800164 log.info("Config file {} not found, using defaults", cfgFile.getPath());
Dave Borowitz03397692012-12-18 17:11:16 -0800165 }
166 this.cfg = cfg;
167
Dave Borowitz09ff62b2012-12-17 14:09:16 -0800168 httpd = new Server();
Dave Borowitz03397692012-12-18 17:11:16 -0800169 httpd.setConnectors(connectors());
170 httpd.setThreadPool(threadPool());
Dave Borowitz09ff62b2012-12-17 14:09:16 -0800171 httpd.setHandler(handler());
172 }
173
174 void start() throws Exception {
175 httpd.start();
176 httpd.join();
177 }
178
Dave Borowitz03397692012-12-18 17:11:16 -0800179 private Connector[] connectors() {
Dave Borowitze1056bb2013-01-11 10:31:37 -0800180 Connector c = new SocketConnector();
Dave Borowitz09ff62b2012-12-17 14:09:16 -0800181 c.setHost(null);
182 c.setPort(cfg.getInt("gitiles", null, "port", 8080));
183 c.setStatsOn(false);
184 return new Connector[]{c};
185 }
186
Dave Borowitz03397692012-12-18 17:11:16 -0800187 private ThreadPool threadPool() {
Dave Borowitz09ff62b2012-12-17 14:09:16 -0800188 QueuedThreadPool pool = new QueuedThreadPool();
189 pool.setName("HTTP");
190 pool.setMinThreads(2);
191 pool.setMaxThreads(10);
192 pool.setMaxQueued(50);
193 return pool;
194 }
195
196 private Handler handler() throws IOException {
197 ContextHandlerCollection handlers = new ContextHandlerCollection();
198 handlers.addHandler(staticHandler());
199 handlers.addHandler(appHandler());
200 return handlers;
Dave Borowitz09ff62b2012-12-17 14:09:16 -0800201 }
202
203 private Handler appHandler() {
Shawn Pearce68311c72015-06-09 17:01:34 -0700204 DebugRenderer renderer = new DebugRenderer(
205 STATIC_PREFIX,
206 Arrays.asList(cfg.getStringList("gitiles", null, "customTemplates")),
207 new File(sourceRoot, "gitiles-servlet/src/main/resources/com/google/gitiles/templates")
208 .getPath(),
209 firstNonNull(cfg.getString("gitiles", null, "siteTitle"), "Gitiles"));
210
211 String docRoot = cfg.getString("gitiles", null, "docroot");
212 Servlet servlet;
213 if (!Strings.isNullOrEmpty(docRoot)) {
214 servlet = createRootedDocServlet(renderer, docRoot);
215 } else {
216 servlet = new GitilesServlet(
217 cfg,
218 renderer,
219 null, null, null, null, null, null, null);
220 }
Dave Borowitz03397692012-12-18 17:11:16 -0800221
Dave Borowitz09ff62b2012-12-17 14:09:16 -0800222 ServletContextHandler handler = new ServletContextHandler();
223 handler.setContextPath("");
Dave Borowitz03397692012-12-18 17:11:16 -0800224 handler.addServlet(new ServletHolder(servlet), "/*");
Dave Borowitz09ff62b2012-12-17 14:09:16 -0800225 return handler;
226 }
227
Dave Borowitz03397692012-12-18 17:11:16 -0800228 private Handler staticHandler() throws IOException {
229 File staticRoot = new File(sourceRoot,
230 "gitiles-servlet/src/main/resources/com/google/gitiles/static");
Dave Borowitz09ff62b2012-12-17 14:09:16 -0800231 ResourceHandler rh = new ResourceHandler();
232 try {
Dave Borowitz03397692012-12-18 17:11:16 -0800233 rh.setBaseResource(new FileResource(staticRoot.toURI().toURL()));
Dave Borowitz09ff62b2012-12-17 14:09:16 -0800234 } catch (URISyntaxException e) {
Dave Borowitz03397692012-12-18 17:11:16 -0800235 throw new IOException(e);
Dave Borowitz09ff62b2012-12-17 14:09:16 -0800236 }
237 rh.setWelcomeFiles(new String[]{});
238 rh.setDirectoriesListed(false);
239 ContextHandler handler = new ContextHandler("/+static");
240 handler.setHandler(rh);
241 return handler;
242 }
Shawn Pearce68311c72015-06-09 17:01:34 -0700243
244 private Servlet createRootedDocServlet(DebugRenderer renderer, String docRoot) {
245 File docRepo = new File(docRoot);
246 final FileKey repoKey = FileKey.exact(docRepo, FS.DETECTED);
247
248 RepositoryResolver<HttpServletRequest> resolver = new RepositoryResolver<HttpServletRequest>() {
249 @Override
250 public Repository open(HttpServletRequest req, String name)
251 throws RepositoryNotFoundException {
252 try {
253 return RepositoryCache.open(repoKey, true);
254 } catch (IOException e) {
255 throw new RepositoryNotFoundException(repoKey.getFile(), e);
256 }
257 }
258 };
259
260 return new RootedDocServlet(
261 resolver,
262 new RootedDocAccess(docRepo),
263 renderer);
264 }
265
266 private class RootedDocAccess implements GitilesAccess.Factory {
267 private final String repoName;
268
269 RootedDocAccess(File docRepo) {
270 if (Constants.DOT_GIT.equals(docRepo.getName())) {
271 repoName = docRepo.getParentFile().getName();
272 } else {
273 repoName = docRepo.getName();
274 }
275 }
276
277 @Override
278 public GitilesAccess forRequest(HttpServletRequest req) {
279 return new GitilesAccess() {
280 @Override
281 public Map<String, RepositoryDescription> listRepositories(Set<String> branches) {
282 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}