SWI-Prolog / pengines

Pengine and Prolog scratchpad
BSD 2-Clause "Simplified" License
57 stars 16 forks source link

http_pengine_send does not accept application/json #15

Open Anniepoo opened 8 years ago

Anniepoo commented 8 years ago

http_pengine_create accepts application/json,

but POST requests to http_pengine_send only accept application/x-www-form-urlencoded

=8cO <-- Annie discovering this while implementing java client

JanWielemaker commented 8 years ago

No. It accepts application/json, but you must tell it you are sending that using the Content-type in the POST header.

Anniepoo commented 8 years ago

TRIGGER WARNING - Java below!

void doAsk(Query query, String ask) throws PengineNotReadyException {
    state.must_be_in(PSt.IDLE, PSt.ASK);

    URL url = po.getActualURL("send");
    StringBuffer response;

// TODO can we abstract this?

    try {
        HttpURLConnection con = (HttpURLConnection) url.openConnection();
        // above should get us an HttpsURLConnection if it's https://...

        //add request header
        con.setRequestMethod("POST");
        con.setRequestProperty("User-Agent", "JavaPengine");
        con.setRequestProperty("Accept", "application/json");
        con.setRequestProperty("Accept-Language", "en-US,en;q=0.5");
        con.setRequestProperty("Content-type", "application/json");

        String urlParameters = po.getRequestBodyAsk(this.getID(), ask);

        // Send post request
        con.setDoOutput(true);
        DataOutputStream wr = new DataOutputStream(con.getOutputStream());
        try {
            wr.writeBytes(urlParameters);
            wr.flush();
        } finally {
            wr.close();
        }

        int responseCode = con.getResponseCode();
        if(responseCode < 200 || responseCode > 299) {
            throw new PengineNotAvailableException("bad response code (if 500, perhaps query was invalid? Or query threw Prolog exception?)" + Integer.toString(responseCode));
        }

        BufferedReader in = new BufferedReader(
                new InputStreamReader(con.getInputStream()));
        String inputLine;
        response = new StringBuffer();
        try {
            while ((inputLine = in.readLine()) != null) {
                response.append(inputLine);
            }
        } finally {
            in.close();
        }

        JsonReaderFactory jrf = Json.createReaderFactory(null);
        JsonReader jr = jrf.createReader(new StringReader(response.toString()));
        JsonObject respObject = jr.readObject();

        JsonString eventjson = (JsonString)respObject.get("event");
        String evtstr = eventjson.getString();

        // TODO need to use this much of it to probe

        if(respObject.containsKey("answer")) {
            handleAnswer(respObject.getJsonObject("answer"));
        }

    } catch (IOException e) {
        state.destroy();
        throw new PengineNotAvailableException(e.toString());
    } catch(SyntaxErrorException e) {
        state.destroy();
        throw new PengineNotAvailableException(e.getMessage());
    }

}
Anniepoo commented 8 years ago

/pengines/create accepts but /pengines/send does not.

On the Java side I'm adding Accept: application/json and Content-Type: application/json headers

I assumed this was me, of course, at first, but the code's pretty clear on the server side. I stepped into it.

http handler calls http_pengine_send first clause fails.

Second clause calls http_parameters, which is eventually going to call http_parms which calls form_data_content_type which in turn needs application/x-www-form-urlencoded

http_pengine_create handles this differently http_pengine_create's 2nd clause does this

memberchk(content_type(CT), Request),
sub_atom(CT, 0, _, _, 'application/json'), !,
http_read_json_dict(Request, Dict),

and is handled correctly.

The code I'm using is essentially copy/paste from my create method.

Java involves much typing. 8cO

JanWielemaker commented 8 years ago

True. /pengine/send expects a Prolog event. Might not be a great id. See the web/js/pengines.js JavaScript client:

Pengine.prototype.send = function(event) {
  var pengine = this;

  $.ajax({ type: "POST",
       url: pengine.options.server +
        '/send?format=' + this.options.format +
        '&id=' + this.id,
       data: event + " .\n",
       contentType: "application/x-prolog; charset=UTF-8",
       success: function(obj) {
         pengine.process_response(obj);
       },
       error: function(jqXHR, textStatus, errorThrown) {
         pengine.error(jqXHR, textStatus, errorThrown);
       }
         });
};

The JavaScript client is probably the best documentation :) One route might be for you to complete the Java client, and before you start other ones, evaluate the oddities and possibly fix them.

Anniepoo commented 8 years ago

OK - thanks for advice about JS client. I haven't looked at it - I've been following the main and Pengines sites and tracing through the Prolog implementation.

Are query strings allowed with POST ? RFC7231 is unclear on the subject, but I suspect it's buried somewhere. Obviously it's common to pass in body because that's what web forms do.

Anyway, I'll change JavaPengines to do this, and submit a fix that lets it use JSON.

Anniepoo commented 8 years ago

And it won't respond with JSON either, even if the accept header is present it responds with raw Prolog.

JanWielemaker commented 8 years ago

Again, see pengines.js:

  $.ajax({ type: "POST",
       url: pengine.options.server +
        '/send?format=' + this.options.format +
        '&id=' + this.id,

I.e, it sends a `format' parameter. Not sure this is a good idea. Some of this is history :( Do you keep a record of oddities?

Anniepoo commented 8 years ago

Ah! Thought that was format TO server thanks.
Not sure what's an oddity, though the API seems a bit jumbled.

wouterbeek commented 8 years ago

@Anniepoo Is there still a remaining issue here or can this be closed?

Anniepoo commented 8 years ago

leave open - its still true. having a workaround isnt a fix

Anniepoo commented 8 years ago

i expect to fix in next week or so