Primeira Rodada de Melhorias no Aplicativo Reddit

Primeira Rodada de Melhorias no Aplicativo Reddit

1. Visão geral

The Reddit web application Case Study está se movendo muito bem - e o pequeno aplicativo da web está se moldando e lentamente se tornando utilizável.

Nesta edição, faremos pequenas melhorias na funcionalidade existente - algumas voltadas para o exterior, outras não - e geralmentemaking the app better.

2. Verificações de configuração

Vamos começar com algumas verificações simples - mas úteis - que precisam ser executadas quando o aplicativo é inicializado:

@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();
}

Observe como estamos usando a anotação@PostConstruct aqui para entrar no ciclo de vida do aplicativo, depois que o processo de injeção de dependência terminar.

Os objetivos simples são:

  • verifique se temos todas as propriedades necessárias para acessar a API do Reddit

  • verifique se a camada de persistência está funcionando (emitindo uma chamada simplesfindAll)

Se falharmos - fazemos isso cedo.

3. O problema do Reddit “Muitos pedidos”

A API Reddit é agressiva em solicitações de limitação de taxa que não enviam um “User-Agent“ exclusivo

Portanto, precisamos adicionar este cabeçalhoUser-Agent exclusivo ao nossoredditRestTemplate - usando umInterceptor personalizado:

3.1. CriarInterceptor personalizado

Aqui está nosso interceptor personalizado -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. ConfigureredditRestTemplate

É claro que precisamos configurar este interceptor com osredditRestTemplate que estamos usando:

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

4. Configurar banco de dados H2 para teste

Em seguida, vamos configurar um banco de dados na memória - H2 - para teste. Precisamos adicionar esta dependência ao nossopom.xml:


    com.h2database
    h2
    1.4.187

E defina umpersistence-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. Mudar para Thymeleaf

JSP está fora e Thymeleaf está dentro

5.1. Modificarpom.xml

Primeiro, precisamos adicionar essas dependências ao nosso pom.xml:


    org.thymeleaf
    thymeleaf
    2.1.4.RELEASE


    org.thymeleaf
    thymeleaf-spring4
    2.1.4.RELEASE


    org.thymeleaf.extras
    thymeleaf-extras-springsecurity3
    2.1.2.RELEASE

5.2. CrieThymeleafConfig

Próximo - um simplesThymeleafConfig:

@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;
    }
}

E adicione-o ao nossoServletInitializer:

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

5.3. Modificarhome.html

E uma rápida modificação da página inicial:

6. Sair

Agora - vamos fazer algumas melhorias que são realmente visíveis para o usuário final do aplicativo. Vamos começar com o logout.

Estamos adicionando uma opção de logout simples ao aplicativo, modificando nossa configuração de segurança:

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

7. Subreddit Autocomplete

Em seguida - vamos implementara simple autocomplete functionality para preencher o subreddit; escrevê-lo manualmente não é uma boa opção, já que há uma boa chance de errar.

Vamos começar com o lado do cliente:




Simples o suficiente. Agora, do lado do servidor:

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

A seguir - vamos ver como verificar se um link já foi enviado antes para o Reddit.

Aqui está nossosubmissionForm.html:




Check if already submitted



E aqui está o nosso método de controle:

@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. Implantação no Heroku

Por fim - vamos configurar a implantação no Heroku - e usar seu nível gratuito para alimentar o aplicativo de amostra.

9.1. Modificarpom.xml

Primeiro, precisaremos adicionar esteWeb Runner plugin aopom.xml:


    org.apache.maven.plugins
    maven-dependency-plugin
    2.3
    
        
            package
            copy
            
                
                    
                        com.github.jsimone
                        webapp-runner
                        7.0.57.2
                        webapp-runner.jar
                    
                
            
        
    

Nota - usaremos o Web Runner para iniciar nosso aplicativo no Heroku.

Vamos usar o Postgresql no Heroku - então precisamos ter uma dependência para o driver:


    org.postgresql
    postgresql
    9.4-1201-jdbc41

9.2. OProcfile

Precisamos definir o processo que será executado no servidor em umProcfile - da seguinte forma:

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

9.3. Criar aplicativo Heroku

Para criar um aplicativo Heroku a partir do seu projeto, vamos simplesmente:

cd path_to_your_project
heroku login
heroku create

9.4. Configuração do banco de dados

Em seguida, precisamos configurar nosso banco de dados usando as propriedades de banco de dadosPostgres do nosso aplicativo.

Por exemplo, aqui está 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

Observe que precisamos obter os detalhes do banco de dados [nome do host, nome do banco de dados, usuário e senha] do painel do Heroku.

Além disso, como na maioria dos casos, a palavra-chave “usuário” é umreserved word in Postgres, então precisamos alterar o nome da nossa tabela de entidades “User”:

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

9.5. Envie o código para Heoku

Agora - vamos enviar o código ao Heroku:

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

10. Conclusão

Nesta quarta parte do nosso Estudo de Caso, o foco foram pequenas, mas importantes melhorias. Se você tem acompanhado, pode ver como isso está se transformando em um pequeno aplicativo interessante e útil.