Thymeleaf 405 Метод запроса 'POST' не поддерживается - PullRequest
0 голосов
/ 07 сентября 2018

Итак, я создаю базовое приложение для социальных сетей для изучения lombok и thymeleaf, и у меня возникают проблемы, когда я пытаюсь удалить запись из mongoDB. Мое приложение может загружать изображения просто отлично, но нажатие клавиши удаления отправит запрос POST вместо запроса DELETE.

index.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>Spring-a-Gram</title>
    <link rel="stylesheet" href="/main.css" />
</head>
<body>

<h1>Spring Boot - Social</h1>

<div>
    <table>
        <thead>
        <tr>
            <th>Id</th><th>Name</th><th>Image</th><th></th>
        </tr>
        </thead>
        <tbody>
        <tr th:each="image : ${images}">
            <td th:text="${image.id}" />
            <td th:text="${image.name}" />
            <td>
                <a th:href="@{'/images/' + ${image.name} + '/raw'}">
                    <img th:src="@{'/images/'+${image.name}+'/raw'}"
                         class="thumbnail" />
                </a>
            </td>
            <td>
                <form th:method="delete"
                      th:action="@{'/images/' + ${image.name}}">
                    <input type="submit" value="Delete" />
                </form>
            </td>
        </tr>
        </tbody>
    </table>

    <form method="post" enctype="multipart/form-data"
          action="/images">
        <p><input type="file" name="file" /></p>
        <p><input type="submit" value="Upload" /></p>
    </form>
</div>

</body>
</html>

HomeController.java

package com.alexander.springsocial;

import org.springframework.core.io.InputStreamResource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.io.IOException;

/**
 * @author Alexander Hartson
 */
@Controller
public class HomeController {

    private static final String BASE_PATH = "/images";
    private static final String FILENAME = "{filename:.+}";

    private final ImageService imageService;

    public HomeController(ImageService imageService) {
        this.imageService = imageService;
    }

    @GetMapping("/")
    public Mono<String> index(Model model) {
        model.addAttribute("images", imageService.findAllImages());
        return Mono.just("index");
    }

    @GetMapping(value = BASE_PATH + "/" + FILENAME + "/raw",
        produces = MediaType.IMAGE_JPEG_VALUE)
    @ResponseBody
    public Mono<ResponseEntity<?>> oneRawImage(
            @PathVariable String filename) {
        return imageService.findOneImage(filename).map(resource -> {
            try {
                return ResponseEntity.ok()
                        .contentLength(resource.contentLength())
                        .body(new InputStreamResource(
                                resource.getInputStream()));
            } catch (IOException e) {
                return ResponseEntity.badRequest()
                        .body("Couldn't find " + filename +
                                " => " + e.getMessage());
            }
        });
    }

    @PostMapping(value = BASE_PATH)
    public Mono<String> createFile(@RequestPart(name = "file")
                                   Flux<FilePart> files) {
        return imageService.createImage(files)
                .then(Mono.just("redirect:/"));
    }

    @DeleteMapping(BASE_PATH + "/" + FILENAME)
    public Mono<String> deleteFile(@PathVariable String filename) {
        return imageService.deleteImage(filename)
                .then(Mono.just("redirect:/"));
    }
}

ImageService.java

package com.alexander.springsocial;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.UUID;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.stereotype.Service;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.FileSystemUtils;

/**
 * @author Alexander Hartson
 */
@Service
public class ImageService {

    public static String UPLOAD_ROOT = "upload-dir";

    // tag::injection[]
    private final ResourceLoader resourceLoader;

    private final ImageRepository imageRepository;

    public ImageService(ResourceLoader resourceLoader,
                        ImageRepository imageRepository) {
        this.resourceLoader = resourceLoader;
        this.imageRepository = imageRepository;
    }
    // end::injection[]

    // tag::1[]
    public Flux<Image> findAllImages() {
        return imageRepository.findAll()
                .log("findAll");
    }
    // end::1[]

    public Mono<Resource> findOneImage(String filename) {
        return Mono.fromSupplier(() ->
                resourceLoader.getResource(
                        "file:" + UPLOAD_ROOT + "/" + filename))
                .log("findOneImage");
    }

    // tag::2[]
    public Mono<Void> createImage(Flux<FilePart> files) {
        return files
                .log("createImage-files")
                .flatMap(file -> {
                    Mono<Image> saveDatabaseImage = imageRepository.save(
                            new Image(
                                    UUID.randomUUID().toString(),
                                    file.filename()))
                            .log("createImage-save");

                    Mono<Void> copyFile = Mono.just(
                            Paths.get(UPLOAD_ROOT, file.filename())
                                    .toFile())
                            .log("createImage-picktarget")
                            .map(destFile -> {
                                try {
                                    destFile.createNewFile();
                                    return destFile;
                                } catch (IOException e) {
                                    throw new RuntimeException(e);
                                }
                            })
                            .log("createImage-newfile")
                            .flatMap(file::transferTo)
                            .log("createImage-copy");

                    return Mono.when(saveDatabaseImage, copyFile)
                            .log("createImage-when");
                })
                .log("createImage-flatMap")
                .then()
                .log("createImage-done");
    }
    // end::2[]

    // tag::3[]
    public Mono<Void> deleteImage(String filename) {
        Mono<Void> deleteDatabaseImage = imageRepository
                .findByName(filename)
                .log("deleteImage-find")
                .flatMap(imageRepository::delete)
                .log("deleteImage-record");

        Mono<Object> deleteFile = Mono.fromRunnable(() -> {
            try {
                Files.deleteIfExists(
                        Paths.get(UPLOAD_ROOT, filename));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        })
                .log("deleteImage-file");

        return Mono.when(deleteDatabaseImage, deleteFile)
                .log("deleteImage-when")
                .then()
                .log("deleteImage-done");
    }
    // end::3[]

    /**
     * Pre-load some test images
     *
     * @return Spring Boot {@link CommandLineRunner} automatically
     *         run after app context is loaded.
     */
    @Bean
    CommandLineRunner setUp() throws IOException {
        return (args) -> {
            FileSystemUtils.deleteRecursively(new File(UPLOAD_ROOT));

            Files.createDirectory(Paths.get(UPLOAD_ROOT));

            FileCopyUtils.copy("Test file",
                    new FileWriter(UPLOAD_ROOT +
                            "/image1.jpg"));

            FileCopyUtils.copy("Test file2",
                    new FileWriter(UPLOAD_ROOT +
                            "/image2.jpg"));

            FileCopyUtils.copy("Test file3",
                    new FileWriter(UPLOAD_ROOT + "/image3.jpg"));
        };
    }
}

Gradle

buildscript {
    ext {
        springBootVersion = '2.0.4.RELEASE'
    }
    repositories {
        mavenCentral()
        maven { url "https://repo.spring.io/snapshot" }
        maven { url "https://repo.spring.io/milestone" }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'com.alexander.spring-social'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
    mavenCentral()
    maven { url "https://repo.spring.io/snapshot" }
    maven { url "https://repo.spring.io/milestone" }
    maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
}


dependencies {
    // tag::netty
    compile('org.springframework.boot:spring-boot-starter-webflux') {
        exclude group: 'org.springframework.boot',
        module: 'spring-boot-starter-reactor-netty'
    }
    compile('org.springframework.boot:spring-boot-starter-tomcat')
    compile('org.springframework.boot:spring-boot-starter-thymeleaf')
    compile('org.springframework.boot:spring-boot-starter-data-mongodb-reactive')
    compile('org.synchronoss.cloud:nio-multipart-parser')
    // Specify lombok version
    compileOnly('org.projectlombok:lombok:1.18.2')
    testCompile('org.springframework.boot:spring-boot-starter-test')
    testCompile("io.projectreactor:reactor-test")
}

enter image description here

Из моей отладки на этом снимке экрана похоже, что метод формы определяет действие как "сообщение", хотя я указал "удалить"? Мне не имеет смысла, почему он это делает. Любые идеи были бы хорошы.

Ответы [ 2 ]

0 голосов
/ 09 сентября 2018

Я думаю, вы немного ошибаетесь в HTTP и HTML. DELETE и PUT являются частью протокола HTTP.

До тех пор, пока HTML 5 DELETE и PUT не будут поддерживаться, и он все еще очень ограничен в зависимости от каждого браузера. Я подозреваю, что тимилиф не поддерживает DELETE как метод формы, что заставляет его изменить его на метод POST.

Я бы предложил изменить ваш метод на POST и заставить ваш контроллер прослушивать POST запросы.

Контроллер, который прослушивает HTTP DELETE запросы, очень полезен, например, для RESTfull services.

0 голосов
/ 08 сентября 2018

Мне удалось это исправить сегодня утром. Я изменяю @DeleteMapping на моем HomeController на @PostMapping. Я не уверен на 100%, почему это решило проблему, я просто знаю, что это работает.

Еще раз спасибо!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...