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="https://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="https://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部では、焦点は小規模ですが重要な改善点でした。これまでフォローしてきたことがあれば、これがどのようにして面白くて便利な小さなアプリになるかを見ることができます。