Première série d’améliorations de l’application Reddit

Première série d'améliorations à l'application Reddit

1. Vue d'ensemble

The Reddit web application Case Study avance bien - et la petite application Web prend forme et devient peu à peu utilisable.

Dans cet épisode, nous allons apporter de petites améliorations aux fonctionnalités existantes - certaines extérieures, d'autres non - et généralementmaking the app better.

2. Vérifications de la configuration

Commençons par quelques vérifications simples mais utiles qui doivent être exécutées lorsque l'application est amorcée:

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

Notez comment nous utilisons ici l'annotation@PostConstruct pour nous connecter au cycle de vie de l'application, une fois le processus d'injection de dépendances terminé.

Les objectifs simples sont:

  • vérifier si nous avons toutes les propriétés nécessaires pour accéder à l'API Reddit

  • vérifier que la couche de persistance fonctionne (en émettant un simple appelfindAll)

Si nous échouons - nous le faisons tôt.

3. Le problème Reddit «Trop de requêtes»

L'API Reddit est agressive dans les requêtes de limitation de débit qui n'envoient pas un "User-Agent" unique.

Donc - nous devons ajouter cet en-tête uniqueUser-Agent à nosredditRestTemplate - en utilisant unInterceptor personnalisé:

3.1. Créer desInterceptor personnalisés

Voici notre intercepteur personnalisé -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. ConfigurerredditRestTemplate

Nous devons bien sûr configurer cet intercepteur avec lesredditRestTemplate que nous utilisons:

@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. Configurer la base de données H2 pour les tests

Ensuite - allons-y et configurez une base de données en mémoire - H2 - pour les tests. Nous devons ajouter cette dépendance à nospom.xml:


    com.h2database
    h2
    1.4.187

Et définissez unpersistence-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. Passer à Thymeleaf

JSP est sorti et Thymeleaf est présent.

5.1. Modifierpom.xml

Premièrement, nous devons ajouter ces dépendances à notre 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. CréerThymeleafConfig

Suivant - un simpleThymeleafConfig:

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

Et ajoutez-le à nosServletInitializer:

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

5.3. Modifierhome.html

Et une modification rapide de la page d'accueil:



Schedule to Reddit




6. Se déconnecter

Maintenant, faisons quelques améliorations qui sont réellement visibles pour l'utilisateur final de l'application. Nous allons commencer par la déconnexion.

Nous ajoutons une option de déconnexion simple dans l'application en modifiant notre configuration de sécurité:

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

7. Sous-reddit Autocomplete

Ensuite - implémentonsa simple autocomplete functionality pour le remplir dans le sous-répertoire; l'écrire manuellement n'est pas une bonne façon de procéder, car il y a de bonnes chances de se tromper.

Commençons par le côté client:




Assez simple. Maintenant, le côté serveur:

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

Ensuite, voyons comment vérifier si un lien est déjà soumis avant Reddit.

Voici nossubmissionForm.html:




Check if already submitted



Et voici notre méthode de contrôleur:

@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. Déploiement sur Heroku

Enfin, nous allons configurer le déploiement sur Heroku et utiliser leur offre gratuite pour alimenter l'exemple d'application.

9.1. Modifierpom.xml

Tout d'abord, nous devrons ajouter ceWeb Runner plugin auxpom.xml:


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

Remarque - nous utiliserons Web Runner pour lancer notre application sur Heroku.

Nous allons utiliser Postgresql sur Heroku - nous devrons donc avoir une dépendance au pilote:


    org.postgresql
    postgresql
    9.4-1201-jdbc41

9.2. LesProcfile

Nous devons définir le processus qui s'exécutera sur le serveur dans unProcfile - comme suit:

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

9.3. Créer une application Heroku

Pour créer une application Heroku à partir de votre projet, nous allons simplement:

cd path_to_your_project
heroku login
heroku create

9.4. Configuration de la base de données

Ensuite, nous devons configurer notre base de données en utilisant les propriétés de base de donnéesPostgresde notre application.

Par exemple, voici 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

Notez que nous devons obtenir les détails de la base de données [nom d’hôte, nom de la base de données, utilisateur et mot de passe] dans le tableau de bord Heroku.

De plus, comme dans la plupart des cas, le mot-clé «utilisateur» est unreserved word in Postgres, nous devons donc changer le nom de notre table d'entités «User»:

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

9.5. Pousser le code vers Heoku

Maintenant, transmettons le code à Heroku:

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

10. Conclusion

Dans cette quatrième partie de notre étude de cas, l'accent était mis sur des améliorations minimes mais importantes. Si vous avez suivi, vous pouvez voir comment cela s'annonce pour devenir une petite application intéressante et utile.