Uri: Einzelne items mit put updaten Expressjs

Hallo,

ich habe ein einzelnes item in meiner Datenbank, dass ich mit PUT updaten möchte. Dazu habe ich ein Formular, das ungefähr so aussehen sollte:

<form action="/things/{id}" method="PUT"> </form>

und serverseitig die ungefähr folgende funktion:

app.put("/things/:id", function(req,res){
    // lange DB-abfrage
    console.log(req.params.id);
    res.redirect("/things/"+req.params.id);

});

Die wird jedenfall überhaupt nicht aufgerufen. wenn ich method="PUT" spezifiziere macht meine APP GET. Ich habe mal irgendwo gehört browser können nur GET und POST. Was kann ich da machen, damit es funktioniert?

Gruß Uri

  1. hi,

    Ich habe mal irgendwo gehört browser können nur GET und POST.

    XHR kann mehr und mit FetchAPI kannst Du außer den well known methods sogar eigene Requestmethoden like WTF definieren.

    MfG

  2. Hallo Uri,

    leider richtig, laut HTML Spec 4 und 5.1 ist im HTML nur GET und POST zugelassen. Alles was nicht POST ist, ist GET.

    REST-API ist für AJAX gedacht.

    In deinem Fall ist es am einfachsten, wenn Du auf REST pfeifst und einfach einen POST machst, der an der Existenz einer ID in der URL erkennt, dass ein existierendes Element aktualisiert werden soll.

    Rolf

    --
    sumpsi - posui - clusi
    1. Hallo Uri,

      leider richtig, laut HTML Spec 4 und 5.1 ist im HTML nur GET und POST zugelassen. Alles was nicht POST ist, ist GET.

      REST-API ist für AJAX gedacht.

      Und da geht auch PUT. MfG

      1. Hallo pl,

        ja sicher, hat keiner bestritten. Aber ich bezweifle, dass es good practice ist, wenn man ein Form mit JavaScript von Hand submitted, NUR UM PUT BENUTZEN ZU KÖNNEN. Da klappt die graceful degradation zu Browsern mit abgeschaltetem JavaScript gar nicht gut.

        Rolf

        --
        sumpsi - posui - clusi
        1. Hi

          ja sicher, hat keiner bestritten. Aber ich bezweifle, dass es good practice ist, wenn man ein Form mit JavaScript von Hand submitted, NUR UM PUT BENUTZEN ZU KÖNNEN. Da klappt die graceful degradation zu Browsern mit abgeschaltetem JavaScript gar nicht gut.

          Ja. Man kann darüber trefflich diskutieren was man eigentlich will. Und ob man damit leben kann, einen REST-Client allein mit JS bauen zu können obwohl das was man damit tut der Browser selbst gar nicht kann. Und was das dann noch für eine Rolle spielt.

          Was kann ich da machen, damit es funktioniert?

          Das ist hier die Frage. Und mit Ajax/PUT ne Datei zum Server schicken um ne Anwendung zu testen ist eine Sache von paar Minuten; wobei man sich natürlich auch einen Client in der Programmiersprache seiner Wahl stricken kann, was verständlicherweise dann auch länger dauert.

          MfG 😉

          1. Danke euch für die Tipps.

            Ich habs mit Ajax gemacht, da ich die App nur baue, um REST zu lernen und tatsächlich wird meine PUT-funktion im controller aufgerufen.

            //serverseitig
            app.put("/buildings", function(req,res){
                 console.log("you got a put Method"); //wird ausgeführt
                 res.redirect("/buildings");          // wird nicht ausgeführt
            
            });
            

            Hier ist mein AJAX

            $('document').ready(function(){
                var containsID=reverseString($(location).attr('href')).split("/");
                var id = containsID[1];
                var xhr = new XMLHttpRequest();
                xhr.open('POST', '/buildings/'+ id , true); // method-override needs it to be POST
                xhr.setRequestHeader('X-HTTP-Method-Override', 'PUT');
                xhr.send();
            });
            
            

            Tatsächlich sagt der browser aber "Cannot POST /buildings/1"

            Woran liegt das?

            Beste Grüße Uri

            1. Hallo Uri,

              erster Anlaufpunkt ist der Netzwerktrace im Browse, oder der Trace des HTTP Servers in node.js. Es gibt bestimmt einen HTTP Statuscode, und ich wette, der beginnt mit 5.

              Es könnte an der Routendefinition im node.js liegen, da wird kein Parameter akzeptiert. Wie es richtig geht, hast Du im Eingangsbeitrag zu diesem Thread selbst geschrieben 😉

              Es könnte aber auch daran liegen, dass Du eine Redirect-Loop hast - Aufruf von /buildings macht einen Redirect zu /buildings.

              Rolf

              --
              sumpsi - posui - clusi
              1. Hallo,

                Wenn ich im Browser in der Entwickler-konsole unter Netzwerk gucke, dann ist da eine 404, was komisch ist, da die app.put() aufgerufen wird. Wie ich den Trace im Server sehen kann, weiß ich nicht.

                Redirect loop kann ich ausschließen. Habe res.render, res.send res.end probiert. wird alles nicht aufgerufen. zumindest nicht im browser sichtbar.

                Ich weiß ehrlich gesagt nicht genau was bei ajax da alles unter der Haube passiert, aber könnte es sein, dass beim submitten erst eine ein POST-Request ausgelöst wird und danach mit ajax nochmal ein PUT request?

                Beste Grüße

                Uri

                1. Hallo Uri,

                  ach Moment, ich Blindfisch. Wie PL schon erwähnte, machst Du mit Ajax einen POST, keinen PUT, das musst Du ändern. Du registrierst einen Handler für PUT, und wenn /buildings dann mit POST kommt, gibt's den 404. Insofern kann es eigentlich gar nicht sein, dass er in console.log("you got a put Method"); //wird ausgeführt ankommt. Es sei denn, du hast noch anderswo Code, den Du hier nicht zeigst.

                  Es hilft auch nichts, wenn Du den gezeigten Ajax-Request im ready-Handler der Seite ausführst. Dann passiert er nur 1x, zu Beginn. Um zu schauen, ob es grundsätzlich überhaupt geht, ist das OK, aber für den gewollten Einsatz musst Du Dich auf das submit-Event des Form registrieren, in der Funktion, die dann aufgerufen wird, den Ajax-Request durchführen und dann die Funktion mit return false; beenden, damit der Browser nicht selbst submittet. Denk auch dran dass der Ajax-Request asynchron ist; wenn Du eine Rückgabe vom PUT verarbeiten willst (z.B. eine OK oder FEHLER Meldung), musst Du noch auf dem XHR einen Handler für das load-Event registrieren und darin die Rückgabe annehmen. Guckst Du mal hier.

                  Rolf

                  --
                  sumpsi - posui - clusi
                  1. Hi,

                    komplettes Beispiel File Upload mit PUT:

                    <input type="file" id="upspot">
                    <button onClick="up()">...</button>
                    
                    <script>
                    
                    function up(){
                        var xhr = new XMLHttpRequest();
                        xhr.open('PUT' , '%url%');
                        xhr.send( document.getElementById('upspot').files[0] );
                    
                        xhr.onload = function(){
                            console.log(this.status, "\n", this.response);
                        };
                    
                    }
                    </script>
                    

                    fertch;

            2. hi,

              Hier ist mein AJAX

              $('document').ready(function(){
                  var containsID=reverseString($(location).attr('href')).split("/");
                  var id = containsID[1];
                  var xhr = new XMLHttpRequest();
                  xhr.open('POST', '/buildings/'+ id , true); // method-override needs it to be POST
                  xhr.setRequestHeader('X-HTTP-Method-Override', 'PUT');
                  xhr.send();
              });
              
              

              Wenn Du ein PUT macht willst, solltest Du auch open('PUT') notieren. Und was soll der proprietäre Header 'X-HTTP-Method-Override' ??? Hast Du dafür was konfiguriert serverseitig?

              Tatsächlich sagt der browser aber "Cannot POST /buildings/1"

              Bitte guck in die Netzwerkkonsole nach dem Status für diesen URL mit Deinem Request. MfG

              1. Hier ist nochmal der vollständige Header:

                Request URL:https://re-manager-amit88.c9users.io/buildings/1
                Request Method:POST
                Status Code:404 Not Found
                Remote Address:35.187.1.119:443
                Referrer Policy:no-referrer-when-downgrade
                Response Headers
                view source
                content-length:151
                content-security-policy:default-src 'self'
                content-type:text/html; charset=utf-8
                date:Thu, 18 Jan 2018 22:45:09 GMT
                vary:X-HTTP-Method-Override
                X-BACKEND:apps-proxy
                x-content-type-options:nosniff
                x-powered-by:Express
                Request Headers
                view source
                Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
                Accept-Encoding:gzip, deflate, br
                Accept-Language:de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7
                Cache-Control:max-age=0
                Connection:keep-alive
                Content-Length:102
                Content-Type:application/x-www-form-urlencoded
                Cookie:c9.live.user.click-through=ok
                Host:re-manager-amit88.c9users.io
                Origin:https://re-manager-amit88.c9users.io
                Referer:https://re-manager-amit88.c9users.io/buildings/1/edit
                Upgrade-Insecure-Requests:1
                User-Agent:Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36
                

                Ich habe mich an dieser Anleitung orientiert.

                entsprechend habe ich

                //serverseitig
                var methodOverride = require('method-override');
                app.use(methodOverride('X-HTTP-Method-Override'));
                ...
                app.put("/buildings/:id", function(req,res){
                  console.log("you got a put Method");
                  res.send('PUT');
                });
                

                Und mein AJAX habe ich jetzt im submit-event handler drin, aber wo ich false zurückgeben soll und was der handler für den load-event machen soll, habe ich jetzt nicht ganz verstanden. Ich habe das jetzt testweise in mein code kopiert, aber es hatte nichts in die Console geloggt.

                $('document').ready(function(){
                    var containsID=reverseString($(location).attr('href')).split("/");
                    var id = containsID[1];
                    $("form").submit( function(event){
                        var xhr = new XMLHttpRequest();
                        xhr.open('POST', '/buildings/'+ id , true); // method-override needs it to be POST
                        xhr.setRequestHeader('X-HTTP-Method-Override', 'PUT');
                        xhr.send();
                        
                        //xhr.onload = function(){
                            //console.log(this.status, "\n", this.response);
                        //};
                
                    });
                });
                

                Gruß und Dank

                Uri

                1. Tach!

                          xhr.open('POST', '/buildings/'+ id , true); // method-override needs it to be POST
                          xhr.setRequestHeader('X-HTTP-Method-Override', 'PUT');
                  

                  Mir scheint, du hast dich ganz schön verwirren lassen. Es gibt nur ein Problem, wenn du ein HTML-Formular mit anderen Methoden als GET und POST auf direktem Wege absenden möchtest. Hingegen haben moderne Browser überhaupt kein Problem, Ajax-Requests mit den anderen Methoden (PUT/DELETE) abzusenden. Für Ajax brauchst du also keine Verrenkungen, solange nicht Uralt-Browser bedient werden müssen.

                  dedlfix.

                  1. hi, ja, mir ist die Sache auch etwas unklar. Ich habe gestern mein edit-template kopiert nach der Vorlage von pl mit dem File-upload probiert es umzusetzen.

                    ich habe jetzt kein formular, dafür ein button mit onclick-attr und input-felder:

                    button( onClick="up()" class="btn btn-primary" id="submit") Submit
                    

                    und mein ajax sieht dann so aus:

                    function up(){
                      
                        var json = {...}
                                
                        var containsID=reverseString($(location).attr('href')).split("/");
                        var id = containsID[1];
                        var xhr = new XMLHttpRequest();
                        xhr.open('PUT', '/buildings/'+ id , true);
                        
                        xhr.send(JSON.stringify(json));
                    }
                    

                    Wenn ich das mache, wird ein request rausgeschickt. Die entwicklerconsole des browsers zeigt Status 200 und mein json, den ich im request mitgeschickt habe. Serverseitig wird die funktion app.put() aufgerufen.

                    Allerdings wird da wie zuvor 1. keine response vom server geschickt und zweitens ist der body vom Request, der beim Server ankommt leer.

                    app.put("/buildings/:id", function(req,res){
                      console.log("you got a put Method"); //Ausgabe: you got a put Method
                      console.log(req.body);               // Ausgabe: {}
                      res.send('PUT');                     // wird nicht ausgeführt
                    
                    });
                    

                    Ich bekomme jetzt keine 404, aber dafür bleib ich nach dem senden auf meiner edit-Seite.

                    Ich habe darauf nochmal rumprobiert und festgestellt, dass mein body auch beim server leer ankommt, wenn ich form verwende und POST mit put überschreibe.

                    Gruß uri

                    1. Tach!

                      Wenn ich das mache, wird ein request rausgeschickt. Die entwicklerconsole des browsers zeigt Status 200 und mein json, den ich im request mitgeschickt habe. Serverseitig wird die funktion app.put() aufgerufen.

                      Allerdings wird da wie zuvor 1. keine response vom server geschickt

                      Eine Response wird geschickt. Der Status 200 ist in der Response enthalten. Du musst die Response im Javacript-Teil entgegennehmen und dann mit Javascript damit machen, was du machen möchtest. Ajax arbeitet ja im Hintergrund, da passiert nichts sichtbares unmittelbar.

                      und zweitens ist der body vom Request, der beim Server ankommt leer.

                      Dazu kann ich nichts sagen, vermute aber mal, dass da ein Fehler in deinem Handling ist. Jedenfalls kannst du in den Entwicklertools des Browsers nachsehen, was im Body von Request und auch Response enthalten ist. Beim Senden zum Server muss ja zumindest auf Clientseite was im Request zu sehen sein, wenn da alles richtig ist. Danach erst geht die Fehlersuche beim Empfänger weiter, also auf Serverseite. Ich kenne Express nicht und kann dir dazu nichts weiter sagen. Vielleicht hat es ja ein generelles Logging, dass dir auch die Vorgänge im inneren des Frameworks protokolliert. Möglicherwiese siehst du nichts, weil der Request bereits anderweitig ausgewertet wurde (unbegründete Spekulation meinerseits).

                      Ich bekomme jetzt keine 404, aber dafür bleib ich nach dem senden auf meiner edit-Seite.

                      Natürlich. Wenn du im Browser eine Änderung haben möchtest, musst du die mi Javascript herbeiführen. Zum Beispiel location.href ändern oder das DOM manipulieren.

                      dedlfix.

                    2. hi,

                      Allerdings wird da wie zuvor 1. keine response vom server geschickt und zweitens ist der body vom Request, der beim Server ankommt leer.

                      Vermutlich hat Dein Request nicht den richtigen Content-Type Header. Der würde beim Upload automatisch gesetzt anhand der angehängten Datei. Du jedoch machst keinen Upload, da solltest Du den Content-Type händisch setzen auf application/json.

                      Und lerne den richtigen Umgang mit der Entwicklerkonsole!

                      MfG

                      1. Stimmt, ich glaube ich habe den falschen content-type

                        Habe jetzt console.log(req.headers) in app.put() gemacht. Das ist die Augabe in der konsole:

                        { host: 're-manager-amit88.c9users.io',
                          'content-length': '131',
                          origin: 'https://re-manager-amit88.c9users.io',
                          'user-agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36',
                          'content-type': 'text/plain;charset=UTF-8',
                          accept: '*/*',
                          referer: 'https://re-manager-amit88.c9users.io/buildings/4/edit',
                          'accept-encoding': 'gzip, deflate, br',
                          'accept-language': 'de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7',
                          cookie: '',
                          'x-forwarded-proto': 'https',
                          'x-forwarded-port': '443',
                          'x-region': 'eu',
                          'x-forwarded-for': '178.12.205.243',
                          connection: 'keep-alive' }
                        

                        Wenn ich es richtig verstanden habe, habe ich 'content-type': 'text/plain;charset=UTF-8', weil ich mit JSON.stringify einen String übergebe. richtig?

                        Wie kann ich den Content-type ändern? mit Google fand ich den NPM modul express-content-type-override, aber ich glaube ich mach damit die Sache wieder kompliziert als sie tatsächlich ist

                        Meine Response ist im übrigen doch beim client zurückgekommen. wenn ich location.href benutze, brauche ich aber eigentlich nicht großartig eine response schicken oder?

                        Gruß Uri

                        1. ok, kam gerade nicht darauf, denn ich den request header clientseitig ändern soll. Macht auch sinn, wenn man darüber nachdenkt. Jetzt gehts.

                          1. ok, kam gerade nicht darauf, denn ich den request header clientseitig ändern soll. Macht auch sinn, wenn man darüber nachdenkt. Jetzt gehts.

                            Na also 😉

                            Schönes Wochenende!

                2. Hier ist nochmal der vollständige Header:

                  vary:X-HTTP-Method-Override
                  

                  Nö, nicht vom Client. Da würde das nämlich so aussehen:

                  X-HTTP-Method-Override: PUT
                  

                  Du selbst hast keinen vary-Header veranlasst sondern einen proprietären Header der mit X- beginnt gesendet und dieser würde serverseitig in eine CGI/1.1 Umgebungsvariable

                  'HTTP_X_HTTP_METHOD_OVERRIDE' => 'PUT',
                  

                  gesetzt werden. Wer auch immer was mit diesen Header anfangen will muss es serverseitig tun. Du präsentierst uns hier die Header eines Proxyservers der entsprechend konfiguriert ist. Das hat mit Deiner Anwendung überhaupt nichts zu tun!

                  Und mein AJAX habe ich jetzt im submit-event handler drin, aber wo ich false zurückgeben soll und was der handler für den load-event machen soll, habe ich jetzt nicht ganz verstanden. Ich habe das jetzt testweise in mein code kopiert, aber es hatte nichts in die Console geloggt.

                  $('document').ready(function(){
                      var containsID=reverseString($(location).attr('href')).split("/");
                      var id = containsID[1];
                      $("form").submit( function(event){
                          var xhr = new XMLHttpRequest();
                          xhr.open('POST', '/buildings/'+ id , true); // method-override needs it to be POST
                          xhr.setRequestHeader('X-HTTP-Method-Override', 'PUT');
                          xhr.send();
                          
                          //xhr.onload = function(){
                              //console.log(this.status, "\n", this.response);
                          //};
                  
                      });
                  });
                  

                  Dein Request geht gar nicht raus!

                  MfG

                  1. Du selbst hast keinen vary-Header veranlasst sondern einen proprietären Header der mit X- beginnt gesendet und dieser würde serverseitig in eine CGI/1.1 Umgebungsvariable

                    Kleine Notiz am Rande, CGI kommt bei Node.js typischerweise nicht zum Einsatz. Die HTTP-Header kann man in einer Express-Anwendung stattdessen mit Request.get auslesen.