Thymeleaf 405请求方法'POST'不支持

时间:2018-09-07 05:33:45

标签: spring mongodb spring-boot thymeleaf

因此,我正在构建一个基本的社交媒体应用程序来学习lombok和百里香叶,当我尝试从mongoDB中删除记录时遇到了问题。我的应用程序可以上传图像,但是按Delete键将发送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"));
        };
    }
}

成绩

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

从我在此屏幕快照中进行的调试看来,即使我指定了“ delete”,form方法仍将动作定义为“ post”?对我来说,为什么这样做是没有意义的。任何想法,将不胜感激。

3 个答案:

答案 0 :(得分:1)

我认为您误会了HTTP和HTML。 DELETEPUT都是HTTP协议的一部分。

直到HTML 5 DELETEPUT均不受支持,但取决于每个浏览器,它仍然非常有限。我怀疑百里香不支持DELETE作为一种表单方法,这导致它将其更改为POST方法。

我建议将您的方法更改为POST,并使您的控制器侦听POST请求。

例如,侦听HTTP DELETE请求的控制器非常有用。 RESTfull服务。

答案 1 :(得分:1)

要支持Th:method =“ delete”标签,您需要一个HiddenHttpMethodFilter。

因此添加

@Bean
HiddenHttpMethodFilter hiddenHttpMethodFilter() {
    return new HiddenHttpMethodFilter();
}

到你的主要班级。

答案 2 :(得分:0)

今天早上我设法修复了它。我将HomeController上的@DeleteMapping更改为@PostMapping。我不是100%知道为什么它可以解决这个问题,我只是知道它可以解决。

再次感谢!