Search code examples
javaspringrestxmlhttprequesthttp-delete

org.springframework.web.HttpRequestMethodNotSupportedException DELETE request denied


Failed to load resource: the server responded with a status of 405.

I'm doing on online course from f-secure and Uni of Helsinki. I passed the test but I can't let me server accept delete request after @CrossOrigin(origins="/**"). The app is a simple task list where a form takes an input and creates a list element. The app also stores the input as objects in another page, /tasks. And when I want to delete them, /tasks won't let me. http.send in the html file gives me the error. How do I make the server accept my delete requests? Some interesting note; I get the same error no matter where I send the delete request. It might aswell be 4140i2t0efsk.org. Same error. CORS is enabled.

Chrome console error:

(index):112 DELETE http://localhost:8080/(random id) 405 ()

XHR failed loading: DELETE "http://localhost:8080/(same random id).

TaskController.java

    package sec.controller;

    import java.util.ArrayList;
    import java.util.List;
    import org.springframework.web.bind.annotation.CrossOrigin;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;

    import org.springframework.web.bind.annotation.RestController;
    import sec.domain.Task;


    @RestController
    @CrossOrigin(origins = "/**")
    @RequestMapping("/tasks")
    public class TaskController {

    private List<Task> tasks;

    public TaskController() {
        this.tasks = new ArrayList<>();
        Task fixme = new Task();
        fixme.setName("A random task.");
        this.tasks.add(fixme);
    }

    @RequestMapping(method = RequestMethod.GET)
    public List<Task> list() {
        return this.tasks;
    }   

  
    @RequestMapping(method = RequestMethod.POST)
    public Task add(@RequestBody Task task) {
        this.tasks.add(task);
        return task;
    }
    
    
    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    public Task delete(@PathVariable String id) {
        Task t = this.tasks.stream().filter(task -> task.getId().equals(id)).findFirst().get();
        this.tasks.remove(t);
        return t;
    }
    }'

tasks.html

    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
    <head lang="en">
        <meta charset="UTF-8" />
        <title>Tasks</title>
    </head>
    <body>

        <h2>Tasks</h2>
        <a href="http://127.0.0.1:8080/tasks">Existing tasks</a>
        <!-- TODO: add the ability to list tasks -->
        <ul id="tasks">
        </ul>


        <form>
            <input type="text" name="name" id="name"/>
            <input type="button" onclick="addTask()" value="Add!"/>
        </form>

        <!-- the javascript has been embedded to the same site -->
        <script th:inline="javascript">
            
            var url = null;

            function loadTasks() {
              console.log("Requesting XML HTTP");
              var http = new XMLHttpRequest();
              http.onreadystatechange = function() {
                  if (http.readyState == 4) {
                      console.log("***READY");
                      if (http.status == 200) {
                          console.log("***STATUS OK");
                          var response = JSON.parse(http.responseText); 
                          console.log("***RESPONSE ", response); // object
                          console.log("***TYPE HTTP RESPONSE", typeof(http.response));
                          console.log("***TYPE RESPONSE", typeof(response));
                          console.log("***HTTP RESPONSE ", http.response); //string
                          console.log("***FOR EACH...");
                          response.forEach(addTaskToList);

                      };
                  } 
             
             }; 
              http.open("GET", "/tasks", true);
              http.send(null); 
            }
           
            function addTask() {
                var name = document.querySelector("#name").value;
                if (!name) {
                    return;
                }

                console.log("**NAME ",name, );

                var http = new XMLHttpRequest();
                http.open("POST", url, true);
                http.setRequestHeader("Content-type", "application/json");
                var data = new Object();
                data.name = name;

                http.onreadystatechange = function () {
                    if (http.readyState === 4) {
                        if (http.status === 200) {
                            addTaskToList(JSON.parse(http.responseText));
                        }
                    }
                };

                http.send(JSON.stringify(data));
            }


            function addTaskToList(task) {
                var liElement = document.createElement("li");
                liElement.id = task.id;
                console.log("***Element id: ", task.id);
                console.log("***Element type:", typeof(liElement));
                liElement.appendChild(document.createTextNode(task.name));
                liElement.addEventListener("click", function(){removeTask(task.id);});
                document.querySelector("#tasks").appendChild(liElement);
                
            }
      
      
            function removeTask(task_id) {
                console.log("***REMOVE TASK: ", task_id);
                document.getElementById(task_id).innerHTML = "Removing...";
                document.getElementById(task_id).remove();
                         
                var http = new XMLHttpRequest();
                
                http.open("DELETE", "/"+(String(task_id)), true);
                http.setRequestHeader("Content-type", "application/json");
                http.onload = function() {
                    tasks = JSON.parse(http.responseText);
                    if (http.readState == 4) {
                        if (http.status == 200) {
                            console.table(tasks);
                            }
                        else {
                            console.error(tasks);
                        }
                    }
                };
                http.send(String(task_id)); //*** HERE IS THE ERROR ***
            }
    
    window.onload = function () {
                loadTasks();
            };
        </script>
    </body>
</html>

Task.java

package sec.domain;

import java.util.UUID;

public class Task {

    private String id;
    private String name;

    public Task() {
        this.id = UUID.randomUUID().toString();
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                            http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>sec</groupId>
    <artifactId>S201.Tasks</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <java.version>1.8</java.version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>    
    
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.1.RELEASE</version>
    </parent>
    
  
    
    <dependencies>   
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
        
        <!-- TMC -->
        <dependency>
            <groupId>fi.helsinki.cs.tmc</groupId>
            <artifactId>edu-test-utils</artifactId>
            <version>0.4.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
        
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <fork>true</fork>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>tmc</id>
            <name>TMC repo</name>
            <url>http://maven.testmycode.net/nexus/content/groups/public</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>tmc</id>
            <name>TMC repo</name>
            <url>http://maven.testmycode.net/nexus/content/groups/public</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>
</project>


Solution

  • Solution

    The problem was the mapping. As you can see in the code, I send my DELETE request to /{id}. Since the objects which I'm trying to delete reside in localhost:8080/tasks, I should've sent the DELETE request to /tasks/{id}.

    Thank you for your help. It took me days to figure a simple problem out.