По всей видимости, написание службы сокращения URL-адресов — это новый « Hello, world! » В мире IoT / microservice / era. Все началось с сервиса сокращения URL в 45 строках Scala — аккуратный кусок Scala, приправленный Spray и Redis для хранения. За этим быстро последовал сервис сокращения URL в 35 строках Clojure и даже URL Shortener в 43 строках Haskell . Так что мой внутренний анти-хипстер спросил: как долго это будет на Яве? Но не ради простой Java, ради бога. Spring Boot с Spring Data Redis — хорошая отправная точка. Все, что нам нужно, это простой контроллер, обрабатывающий GET и POST:
import com.google.common.hash.Hashing;
import org.apache.commons.validator.routines.UrlValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.*;
import java.nio.charset.StandardCharsets;
@org.springframework.boot.autoconfigure.EnableAutoConfiguration
@org.springframework.stereotype.Controller
public class UrlShortener {
public static void main(String[] args) {
SpringApplication.run(UrlShortener.class, args);
}
@Autowired private StringRedisTemplate redis;
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public void redirect(@PathVariable String id, HttpServletResponse resp) throws Exception {
final String url = redis.opsForValue().get(id);
if (url != null)
resp.sendRedirect(url);
else
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
}
@RequestMapping(method = RequestMethod.POST)
public ResponseEntity<String> save(HttpServletRequest req) {
final String queryParams = (req.getQueryString() != null) ? "?" + req.getQueryString() : "";
final String url = (req.getRequestURI() + queryParams).substring(1);
final UrlValidator urlValidator = new UrlValidator(new String[]{"http", "https"});
if (urlValidator.isValid(url)) {
final String id = Hashing.murmur3_32().hashString(url, StandardCharsets.UTF_8).toString();
redis.opsForValue().set(id, url);
return new ResponseEntity<>("http://mydomain.com/" + id, HttpStatus.OK);
} else
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}
Код хорошо нагляден и функционально эквивалентен версии в Scala. Я не пытался сжать его слишком сильно, чтобы счетчик строк был как можно короче, приведенный выше код довольно типичен, с некоторыми подробностями:
- Я обычно не использую символы подстановки
- Я не использую полностью квалифицированные имена классов (я хочу сохранить одну
importстроку, я признаю) - Окружаю
if/elseблокирую брекетами - Я почти никогда не использую полевую инъекцию, самый уродливый брат в инверсии семейства контроля. Вместо этого я бы выбрал конструктор, чтобы разрешить тестирование с использованием мошеннического Redis:
private final StringRedisTemplate redis; @Autowired public UrlShortener(StringRedisTemplate redis) { this.redis = redis; }
Больше всего я боролся за то, чтобы получить оригинальный полный URL. В основном мне нужно все после
.comили порт. Никакого кровавого пути (ни сервлетов, ни Spring MVC), отсюда и неуклюжая
getQueryString()возня. Вы можете использовать сервис следующим образом — создать более короткий URL:
$ curl -vX POST localhost:8080/https://www.google.pl/search?q=tomasz+nurkiewicz > POST /https://www.google.pl/search?q=tomasz+nurkiewicz HTTP/1.1 > User-Agent: curl/7.30.0 > Host: localhost:8080 > Accept: */* > < HTTP/1.1 200 OK < Server: Apache-Coyote/1.1 < Content-Type: text/plain;charset=ISO-8859-1 < Content-Length: 28 < Date: Sat, 23 Aug 2014 20:47:40 GMT < http://mydomain.com/50784f51
Перенаправление через более короткий URL:
$ curl -v localhost:8080/50784f51 > GET /50784f51 HTTP/1.1 > User-Agent: curl/7.30.0 > Host: localhost:8080 > Accept: */* > < HTTP/1.1 302 Found < Server: Apache-Coyote/1.1 < Location: https://www.google.pl/search?q=tomasz+nurkiewicz < Content-Length: 0 < Date: Sat, 23 Aug 2014 20:48:00 GMT <
Для полноты, вот файл сборки в Gradle (также будет работать maven), пропущенный во всех предыдущих решениях:
buildscript {
repositories {
mavenLocal()
maven { url "http://repo.spring.io/libs-snapshot" }
mavenCentral()
}
dependencies {
classpath 'org.springframework.boot:spring-boot-gradle-plugin:1.1.5.RELEASE'
}
}
apply plugin: 'java'
apply plugin: 'spring-boot'
sourceCompatibility = '1.8'
repositories {
mavenLocal()
maven { url 'http://repository.codehaus.org' }
maven { url 'http://repo.spring.io/milestone' }
mavenCentral()
}
dependencies {
compile "org.springframework.boot:spring-boot-starter-web:1.1.5.RELEASE"
compile "org.springframework.boot:spring-boot-starter-redis:1.1.5.RELEASE"
compile 'com.google.guava:guava:17.0'
compile 'org.apache.commons:commons-lang3:3.3.2'
compile 'commons-validator:commons-validator:1.4.0'
compile 'org.apache.tomcat.embed:tomcat-embed-el:8.0.9'
compile "org.aspectj:aspectjrt:1.8.1"
runtime "cglib:cglib-nodep:3.1"
}
tasks.withType(GroovyCompile) {
groovyOptions.optimizationOptions.indy = true
}
task wrapper(type: Wrapper) {
gradleVersion = '2.0'
}
На самом деле также 42 строки … Вот и все приложение, без XML, без дескрипторов, без установки.
Я не отношусь к этому упражнению как к фиктивному коду для кратчайшего, наиболее запутанного рабочего кода. Веб-сервис сокращения URL с бэкэндом Redis — интересная демонстрация синтаксиса и возможностей данного языка и экосистемы. Гораздо интереснее, чем куча алгоритмических проблем, например, в
коде Розетты . Также это хороший минимальный шаблон для написания REST-сервиса.
Одна важная особенность
оригинальной реализации Scala , о которой как-то молча забыли во всех реализациях, включая эту, заключается в том, что она не блокируется. Доступ к HTTP и Redis зависит от события (
реактивныйХорошо, я сказал это), таким образом, я полагаю, что он может обрабатывать десятки тысяч клиентов одновременно. Этого нельзя достичь с помощью блокирующих контроллеров, поддерживаемых Tomcat. Но все же вы должны признать, что такой сервис, написанный на Java (даже не на Java 8!), Удивительно лаконичен, прост и понятен — ни одно из других решений не является настолько читабельным (это, конечно, субъективно).
В ожидании других!