Redditアプリケーションの改良の第1ラウンド

1概要

Reddit Webアプリケーションのリンク:/case-study-a-reddit-app-with-[ケーススタディ]

今回の記事では、既存の機能を少し改善し(一部は外部向け、もう一部は公開しない)、一般的には アプリをより良くする ことにします。

2セットアップチェック

アプリケーションがブートストラップされたときに実行する必要がある簡単な(しかし便利な)チェックから始めましょう:

@Autowired
private UserRepository repo;

@PostConstruct
public void startupCheck() {
    if (StringUtils.isBlank(accessTokenUri) ||
      StringUtils.isBlank(userAuthorizationUri) ||
      StringUtils.isBlank(clientID) || StringUtils.isBlank(clientSecret)) {
        throw new RuntimeException("Incomplete reddit properties");
    }
    repo.findAll();
}

依存性注入プロセスが終了した後、ここで @ PostConstruct アノテーションを使用してアプリケーションのライフサイクルにフックする方法に注目してください。

簡単な目標は次のとおりです。

  • Reddit APIにアクセスするために必要なすべてのプロパティがあるか確認してください

  • 永続層が機能していることを確認する

findAll call)

失敗した場合 - 早くそうします。

3 「あまりにも多くの要求」Reddit問題

Reddit APIは、ユニークな「 User-Agent 」を送信していないリクエストをレート制限することに積極的です。

そのため、独自の Interceptor を使用して、この一意の User-Agent ヘッダーを redditRestTemplate に追加する必要があります。

3.1. カスタム Interceptor を作成

これが私たちのカスタムインターセプターです - UserAgentInterceptor :

public class UserAgentInterceptor implements ClientHttpRequestInterceptor {

    @Override
    public ClientHttpResponse intercept(
      HttpRequest request, byte[]body,
      ClientHttpRequestExecution execution) throws IOException {

        HttpHeaders headers = request.getHeaders();
        headers.add("User-Agent", "Schedule with Reddit");
        return execution.execute(request, body);
    }
}

3.2. redditRestTemplate を設定します.

もちろん、このインターセプターを使用している redditRestTemplate で設定する必要があります。

@Bean
public OAuth2RestTemplate redditRestTemplate(OAuth2ClientContext clientContext) {
    OAuth2RestTemplate template = new OAuth2RestTemplate(reddit(), clientContext);
    List<ClientHttpRequestInterceptor> list = new ArrayList<ClientHttpRequestInterceptor>();
    list.add(new UserAgentInterceptor());
    template.setInterceptors(list);
    return template;
}

4テスト用のH2データベースの設定

次に、テストのためにインメモリーDB - H2を設定しましょう。この依存関係を pom.xml に追加する必要があります。

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.4.187</version>
</dependency>

そして persistence-test.properties を定義します。

## DataSource Configuration ###
jdbc.driverClassName=org.h2.Driver
jdbc.url=jdbc:h2:mem:oauth__reddit;DB__CLOSE__DELAY=-1
jdbc.user=sa
jdbc.pass=
## Hibernate Configuration ##
hibernate.dialect=org.hibernate.dialect.H2Dialect
hibernate.hbm2ddl.auto=update

5タイムリーフ に切り替える

JSPはアウト、Thymeleafはインです。

5.1. pom.xml を変更します

まず、pom.xmlにこれらの依存関係を追加する必要があります。

<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf</artifactId>
    <version>2.1.4.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring4</artifactId>
    <version>2.1.4.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity3</artifactId>
    <version>2.1.2.RELEASE</version>
</dependency>

5.2. ThymeleafConfig を作成します

次に - 単純な ThymeleafConfig :

@Configuration
public class ThymeleafConfig {
    @Bean
    public TemplateResolver templateResolver() {
        ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver();
        templateResolver.setPrefix("/WEB-INF/jsp/");
        templateResolver.setSuffix(".jsp");
        return templateResolver;
    }

    @Bean
    public SpringTemplateEngine templateEngine() {
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(templateResolver());
        templateEngine.addDialect(new SpringSecurityDialect());
        return templateEngine;
    }

    @Bean
    public ViewResolver viewResolver() {
        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
        viewResolver.setTemplateEngine(templateEngine());
        viewResolver.setOrder(1);
        return viewResolver;
    }
}

それを ServletInitializer に追加します。

@Override
protected WebApplicationContext createServletApplicationContext() {
    AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
    context.register(PersistenceJPAConfig.class, WebConfig.class,
      SecurityConfig.class, ThymeleafConfig.class);
    return context;
}

5.3. home.html を変更します

ホームページの簡単な修正:

<html>
<head>
<title>Schedule to Reddit</title>
</head>
<body>
<div class="container">
        <h1>Welcome, <small><span sec:authentication="principal.username">Bob</span></small></h1>
        <br/>
        <a href="posts" >My Scheduled Posts</a>
        <a href="post" >Post to Reddit</a>
        <a href="postSchedule" >Schedule Post to Reddit</a>
</div>
</body>
</html>

6. ログアウト

それでは - 実際にアプリケーションのエンドユーザーに見えるいくつかの改善をしましょう。ログアウトから始めましょう。

セキュリティ設定を変更して、アプリケーションに簡単なログアウトオプションを追加します。

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .....
        .and()
        .logout()
        .deleteCookies("JSESSIONID")
        .logoutUrl("/logout")
        .logoutSuccessUrl("/");
}

7. サブクレジットオートコンプリート

次に、サブクレジットを埋めるための 単純なオートコンプリート機能 を実装しましょう。手作業でそれを書くことはそれを誤解するかなりの機会があるので行くのに良い方法ではありません。

クライアントサイドから始めましょう:

<input id="sr" name="sr"/>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.2/jquery-ui.min.js"></script>
<script>
$(function() {
    $( "#sr" ).autocomplete({
        source: "/subredditAutoComplete"
    });
});
</script>

とても簡単です。今、サーバー側:

@RequestMapping(value = "/subredditAutoComplete")
@ResponseBody
public String subredditAutoComplete(@RequestParam("term") String term) {
    MultiValueMap<String, String> param = new LinkedMultiValueMap<String, String>();
    param.add("query", term);
    JsonNode node = redditRestTemplate.postForObject(
      "https://oauth.reddit.com//api/search__reddit__names", param, JsonNode.class);
    return node.get("names").toString();
}

8リンクがすでにReddit になっているかどうか確認する

次に、リンクが既にRedditに送信されているかどうかを確認する方法を見てみましょう。

これが submissionForm.html です。

<input name="url"/>
<input name="sr">

<a href="#" onclick="checkIfAlreadySubmitted()">Check if already submitted</a>
<span id="checkResult" style="display:none"></span>

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script>
$(function() {
    $("input[name='url'],input[name='sr']").focus(function (){
        $("#checkResult").hide();
    });
});
function checkIfAlreadySubmitted(){
    var url = $("input[name='url']").val();
    var sr = $("input[name='sr']").val();
    if(url.length >3 && sr.length > 3){
        $.post("checkIfAlreadySubmitted",{url: url, sr: sr}, function(data){
            var result = JSON.parse(data);
            if(result.length == 0){
                $("#checkResult").show().html("Not submitted before");
            }else{
                $("#checkResult").show().html(
               'Already submitted <b><a target="__blank" href="http://www.reddit.com'
               +result[0].data.permalink+'">here</a></b>');
            }
        });
    }
    else{
        $("#checkResult").show().html("Too short url and/or subreddit");
    }
}
</script>

そして、これが私たちのコントローラメソッドです。

@RequestMapping(value = "/checkIfAlreadySubmitted", method = RequestMethod.POST)
@ResponseBody
public String checkIfAlreadySubmitted(
  @RequestParam("url") String url, @RequestParam("sr") String sr) {
    JsonNode node = redditRestTemplate.getForObject(
      "https://oauth.reddit.com/r/" + sr + "/search?q=url:" + url + "&restrict__sr=on", JsonNode.class);
    return node.get("data").get("children").toString();
}

9 Herokuへの展開

最後に、Herokuへのデプロイを設定します。そして、その無料利用枠を使用してサンプルアプリを起動します。

9.1. pom.xml を変更します

まず、この Web Runnerプラグイン pom.xml に追加する必要があります。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <version>2.3</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals><goal>copy</goal></goals>
            <configuration>
                <artifactItems>
                    <artifactItem>
                        <groupId>com.github.jsimone</groupId>
                        <artifactId>webapp-runner</artifactId>
                        <version>7.0.57.2</version>
                        <destFileName>webapp-runner.jar</destFileName>
                    </artifactItem>
                </artifactItems>
            </configuration>
        </execution>
    </executions>
</plugin>

注 - Herokuでアプリを起動するにはWeb Runnerを使用します。

HerokuではPostgresqlを使用することになるので、ドライバに依存する必要があります。

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>9.4-1201-jdbc41</version>
</dependency>

9.2. Procfile

次のように、サーバー上で実行されるプロセスを Procfile に定義する必要があります。

web:    java $JAVA__OPTS -jar target/dependency/webapp-runner.jar --port $PORT target/** .war

9.3. Herokuアプリを作成

プロジェクトからHerokuアプリを作成するには、次のようにします。

cd path__to__your__project
heroku login
heroku create

9.4. データベース構成

次に - 私たちのアプリdo__Postgresデータベースプロパティを使ってデータベースを設定する必要があります。

たとえば、persistence-prod.propertiesです。

## DataSource Configuration ##
jdbc.driverClassName=org.postgresql.Driver
jdbc.url=jdbc:postgresql://hostname:5432/databasename
jdbc.user=xxxxxxxxxxxxxx
jdbc.pass=xxxxxxxxxxxxxxxxx

## Hibernate Configuration ##
hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
hibernate.hbm2ddl.auto=update

データベースの詳細[ホスト名、データベース名、ユーザー、パスワード]をHerokuダッシュボードから取得する必要があることに注意してください。

また、ほとんどの場合、「user」というキーワードはhttp://www.postgresql.org/docs/7.3/static/sql-keywords-appendix.html[ Postgres ]内の予約語です。 “ User ”エンティティテーブル名:

@Entity
@Table(name = "APP__USER")
public class User { .... }

9.5. Heokuにコードをプッシュする

それでは、Herokuにコードをプッシュしましょう。

git add .
git commit -m "init"
git push heroku master

10結論

今回のケーススタディの第4部では、焦点は小規模ですが重要な改善点でした。これまでフォローしてきたことがあれば、これがどのようにして面白くて便利な小さなアプリになるかを見ることができます。

前の投稿:JavaでのDijkstraアルゴリズム
次の投稿:Java Web Weekly 52