ESP8266, how to make post request with graphQL - api

I am very new to programming with ESP8266 and can't get this to work.
I am successfully connected to the internet.
With the <ESP8266HTTPClient.h> I am writing the following code:
void loop() {
if (WiFi.status() == WL_CONNECTED) { //Check WiFi connection status
HTTPClient http; //Declare object of class HTTPClient
http.begin("https://api.entur.io/journey-planner/v2/graphql"); //Specify request destination
http.addHeader("Content-Type", "application/json"); //Specify content-type header
http.addHeader("ET-Client-Name", "student-bussAPI"); //Identification requested by the API
int httpCode = http.POST("{\"query\":\"{authorities{id}}\"}"); //Send the request
String payload = http.getString(); //Get the response payload
Serial.println(httpCode); //Print HTTP return code
Serial.println(payload); //Print request response payload
http.end(); //Close connection
} else {
Serial.println("Error in WiFi connection");
}
delay(30000); //Send a request every 30 seconds
}
the code runs with serial output -1
I have tested the query request on postman, and it worked fine there.

The "-1" you are getting is caused by the attempt to establish a connection to a strictly HTTPS service via HTTP.
As the code examples in the ESP8266HttpClient library explain, "httpCode will be negative on error" - what you are seeing is the printing of a http status code of -1.
The easiest way to achieve what you are trying to do here (Query a HTTPS API via the ESP8266httpClient library) would be to connect to it using the current SHA-1 fingerprint of the certificate used by the service as a second parameter of your call to "http.begin".
Example to answer your specific question:
HTTPClient http; //Declare object of class HTTPClient
const char* host = "https://api.entur.io/journey-planner/v2/graphql";
const char* fingerprint ="Current sha-1 fingerprint goes here";
http.begin(host, fingerprint); //Specify request destination and fingerprint for cert to make it do HTTPS
http.addHeader("Content-Type", "application/graphql"); //Content type here is important; Its not JSON
http.addHeader("ET-Client-Name", "Esp8266-BitBrb");
I should also mention that the specific API you are using here has a strict check on content type, so you should change your header to match the data type the API accepts, "application/graphql", or else you'll have trouble :)
Incidentally, i have the full code from a project i did last year with the same service you are using (Hi, fellow Norwegian and EnTur user) available here: https://www.bitbrb.com/electronics/graphql-querying-a-bus
I see they got a new certificate earlier this year, other than that you should be able to cut and paste.
good luck :)

Related

Nginx reverse proxy - 405 POST

I have Asp.Net Core Web Api application, which uses "x-api-key" http header to authorize a person sending a request. I've setup action filter as
public void OnActionExecuting(ActionExecutingContext context)
{
// Retrieve record with specified api key
var api = dbContext.Apis
.Include(a => a.User)
.FirstOrDefault(a => a.Key.Equals(context.HttpContext.Request.Headers["x-api-key"]));
// Check if record exists
if (api is null)
{
context.Result = new UnauthorizedResult(); // short circuit and return 401
}
}
It is working as expected on both GET and POST requests without nginx proxy, however as soon as I add nginx, I receive 405 Not Allowed on POST request if api key is invalid but 401 on GET (if api key is valid filter works as expected and passes execution to controller). Here is my proxy configuration
server {
listen 80;
location / {
proxy_pass http://ctoxweb:5000;
}
}
(Both nginx and web api are setup using docker). What's the problem and how to fix that?
I managed to fix this problem, however I don't know exactly why this happens, I suppose it's somehow related to nginx not allowing any method (except for GET) on static content. My best guess is that nginx assumes that empty response body (which comes from new UnauthorizedResult()) is static, though it's clearly supplied by backend. The way to fix it is as easy as supply some object to response body, for example
if (api is null)
{
context.HttpContext.Response.ContentType = "application/json";
context.Result = new UnautorizedObjectResult("{\"info\":\"no api key header present\"}");
}

POST to REST API from my ESP8266

I've made a REST API and I'd like to do a post request to one of the endpoints from my ESP8266, but I can't manage to do so.
The code inside the loop so far:
HTTPClient http; //Declare object of class HTTPClient
http.begin("http://localhost:5000/api/users/5b1e82fb8c620238a85646fc/arduinos/5b243dc666c18a2e10eb4097/data");
http.addHeader("Content-Type", "text/plain");
http.addHeader("Authorization", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjViMWU4MmZiOGM2MjAyMzhhODU2NDZmYyIsImlhdCI6MTUyOTEwMTc5MiwiZXhwIjoxNTI5MTE2MTkyfQ.2O6knqriuFoEW9C2JQKRlM3D0DNnzqC7e7gpidy3pWU");
http.end();
The problem is that I don't know how to set the body of the request.
It should be a json with a single key called "value". For instance:
{
"value":101
}
Anyone knows how to do it? Also it's probable that I should use the ip instead of "localhost".
Thanks in advance.
Use ArduinoJson Library here. Then you can build your HTTP body.
StaticJsonBuffer<300> JSONbuffer; //Declaring static JSON buffer
JsonObject& JSONencoder = JSONbuffer.createObject();
JSONencoder["value"] = value_var;
char JSONmessageBuffer[300];
JSONencoder.prettyPrintTo(JSONmessageBuffer, sizeof(JSONmessageBuffer));
HTTPClient http; //Declare object of class HTTPClient
http.begin("API end point here"); //Specify request destination
http.addHeader("Content-Type", "application/json"); //Specify content-type header
int httpCode = http.POST(JSONmessageBuffer); //Send the request
String payload = http.getString(); //Get the response payload
Then use the above sample code to encapsulate JSON and send it to the API endpoint.

Google OAuth 2.0 for desktop apps for Windows without Admin privileges

I've heard about Google's plan of modernizing OAuth interactions described here: https://developers.googleblog.com/2016/08/modernizing-oauth-interactions-in-native-apps.html
Then I was looking at the sample desktop application for Windows found here: https://github.com/googlesamples/oauth-apps-for-windows/tree/master/OAuthDesktopApp.
It's pretty simple and it was working, but once I started Visual Studio without elevated privileges (as a non-admin), I experienced that the HttpListener was not able to start because of the following error: "Access Denied".
It turned out that starting an HttpListener at the loopback address (127.0.0.1) is not possible without admin rights. However trying localhost instead of 127.0.0.1 lead to success.
I found that there is a specific command that allows HttpListener to start at the given address (and port):
netsh http add urlacl url=http://+:80/MyUri user=DOMAIN\user
But it also can be only executed with admin rights, so it's not an option.
Still localhost seems to be the best shot but OAuth 2.0 for Mobile & Desktop Apps states the following regarding this section:
See the redirect_uri parameter definition for more information about the loopback IP address. It is also possible to use localhost in place of the loopback IP, but this may cause issues with client firewalls. Most, but not all, firewalls allow loopback communication.
This is why I'm a bit suspicious to use localhost. So I'm wondering what is the recommended way of Google in this case, as I'm not intending to run our application as administrator just for this reason.
Any ideas?
You can use TcpListener for instance instead of HttpListener. It does not need elevation to listen.
The following is a modified excerpt of this sample:
https://github.com/googlesamples/oauth-apps-for-windows/tree/master/OAuthDesktopApp
// Generates state and PKCE values.
string state = randomDataBase64url(32);
string code_verifier = randomDataBase64url(32);
string code_challenge = base64urlencodeNoPadding(sha256(code_verifier));
const string code_challenge_method = "S256";
// Creates a redirect URI using an available port on the loopback address.
var listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();
string redirectURI = string.Format("http://{0}:{1}/", IPAddress.Loopback, ((IPEndPoint)listener.LocalEndpoint).Port);
output("redirect URI: " + redirectURI);
// Creates the OAuth 2.0 authorization request.
string authorizationRequest = string.Format("{0}?response_type=code&scope=openid%20profile&redirect_uri={1}&client_id={2}&state={3}&code_challenge={4}&code_challenge_method={5}",
authorizationEndpoint,
System.Uri.EscapeDataString(redirectURI),
clientID,
state,
code_challenge,
code_challenge_method);
// Opens request in the browser.
System.Diagnostics.Process.Start(authorizationRequest);
// Waits for the OAuth authorization response.
var client = await listener.AcceptTcpClientAsync();
// Read response.
var response = ReadString(client);
// Brings this app back to the foreground.
this.Activate();
// Sends an HTTP response to the browser.
WriteStringAsync(client, "<html><head><meta http-equiv='refresh' content='10;url=https://google.com'></head><body>Please close this window and return to the app.</body></html>").ContinueWith(t =>
{
client.Dispose();
listener.Stop();
Console.WriteLine("HTTP server stopped.");
});
// TODO: Check the response here to get the authorization code and verify the code challenge
The read and write methods being:
private string ReadString(TcpClient client)
{
var readBuffer = new byte[client.ReceiveBufferSize];
string fullServerReply = null;
using (var inStream = new MemoryStream())
{
var stream = client.GetStream();
while (stream.DataAvailable)
{
var numberOfBytesRead = stream.Read(readBuffer, 0, readBuffer.Length);
if (numberOfBytesRead <= 0)
break;
inStream.Write(readBuffer, 0, numberOfBytesRead);
}
fullServerReply = Encoding.UTF8.GetString(inStream.ToArray());
}
return fullServerReply;
}
private Task WriteStringAsync(TcpClient client, string str)
{
return Task.Run(() =>
{
using (var writer = new StreamWriter(client.GetStream(), Encoding.UTF8))
{
writer.Write("HTTP/1.0 200 OK");
writer.Write(Environment.NewLine);
writer.Write("Content-Type: text/html; charset=UTF-8");
writer.Write(Environment.NewLine);
writer.Write("Content-Length: " + str.Length);
writer.Write(Environment.NewLine);
writer.Write(Environment.NewLine);
writer.Write(str);
}
});
}
By default there is a URL pattern http://+:80/Temporary_Listen_Addresses/ which is allowed for all users (\Everyone)
You can use this as a prefix for your listener. More generally (to avoid collisions with other listeners) you should generate a URL under Temporary_Listen_Addresses (e.g. using a GUID) and use that as your listener prefix.
Unfortunately, a sysadmin can use netsh http to delete this entry or to restrict its usage to only certain users. Also, this does not appear to support listening for an HTTPS request as there is no corresponding ACL entry for port 443.
An admin can list all these permitted URL patterns using netsh http show urlacl as a command.

Apache Http Client Put Request Error

I'm trying to upload a file using the Apache Http Client's PUT method. The code is as below;
def putFile(resource: String, file: File): (Int, String) = {
val httpClient = new DefaultHttpClient(connManager)
httpClient.getCredentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(un, pw))
val url = address + "/" + resource
val put = new HttpPut(url)
put.setEntity(new FileEntity(file, "application/xml"))
executeHttp(httpClient, put) match {
case Success(answer) => (answer.getStatusLine.getStatusCode, "Successfully uploaded file")
case Failure(e) => {
e.printStackTrace()
(-1, e.getMessage)
}
}
}
When I tried running the method, I get to see the following error:
org.apache.http.NoHttpResponseException: The target server failed to respond
at org.apache.http.impl.conn.DefaultResponseParser.parseHead(DefaultResponseParser.java:101)
at org.apache.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:252)
at org.apache.http.impl.AbstractHttpClientConnection.receiveResponseHeader(AbstractHttpClientConnection.java:281)
at org.apache.http.impl.conn.DefaultClientConnection.receiveResponseHeader(DefaultClientConnection.java:247)
at org.apache.http.impl.conn.AbstractClientConnAdapter.receiveResponseHeader(AbstractClientConnAdapter.java:219)
at org.apache.http.protocol.HttpRequestExecutor.doReceiveResponse(HttpRequestExecutor.java:298)
at org.apache.http.protocol.HttpRequestExecutor.execute(HttpRequestExecutor.java:125)
at org.apache.http.impl.client.DefaultRequestDirector.tryExecute(DefaultRequestDirector.java:633)
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:454)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:820)
I do not know what has gone wrong? I'm able to do GET requests, but PUT seems not to work! Any clues as to where I should look for?
Look on the server. If GET Works, but PUT does not, then you have to figure out the receiving end.
Also, you may want to write a simple HTML File that has a form with PUT Method in it to rule out your Java Part.
As a sidenode: Its technically possible that something in between stops the request from going through or the response reaching you. Best setup a dummy HTTP Server to do the testing against.
Maybe its also a timeout issue, so the server takes to long to process your PUT.
The connection you are trying to use is a stale connection and therefore the request is failing.
But why are you only seeing an error for the PUT request and you are not seeing it for the GET request?
If you check the DefaultHttpRequestRetryHandler class you will see that by default HttpClient attempts to automatically recover from I/O exceptions. The default auto-recovery mechanism is limited to just a few exceptions that are known to be safe.
HttpClient will make no attempt to recover from any logical or HTTP protocol errors (those derived from HttpException class).
HttpClient will automatically retry those methods that are assumed to be idempotent. Your GET request, but not your PUT request!!
HttpClient will automatically retry those methods that fail with a transport exception while the HTTP request is still being transmitted to the target server (i.e. the request has not been fully transmitted to the server).
This is why you don't notice any error with your GET request, because the retry mechanism handles it.
You should define a CustomHttpRequestRetryHandler extending the DefaultHttpRequestRetryHandler. Something like this:
public class CustomHttpRequestRetryHandler extends DefaultHttpRequestRetryHandler {
#Override
public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
if(exception instanceof NoHttpResponseException) {
return true;
}
return super.retryRequest(exception, executionCount, context);
}
}
Then just assign your CustomHttpRequestRetryHandler
final HttpClientBuilder httpClientBuilder = HttpClients.custom();
httpClientBuilder.setRetryHandler(new CustomHttpRequestRetryHandler());
And that's it, now your PUT request is handled by your new RetryHandler (like the GET was by the default one)

GCM server side implementation for java

I need to implement a standalone application for the server side of gcm to push notifications to the device. Is there any reference i could get other than the one on the Getting started page.People say something about xmpp. Do we need to use this or can we directly use the gcm server side methods.Help.Or is there any other easy way to implement this.I hope i put my question properly.
Here is nice tutorial for GCM server side implementation for java.
URL: java gcm server side implementation
Example code: java gcm server side implementation`{
new Thread(){
public void run(){
try {
//Please add here your project API key: "Key for browser apps (with referers)".
//If you added "API key Key for server apps (with IP locking)" or "Key for Android apps (with certificates)" here
//then you may get error responses.
Sender sender = new Sender("AIzaSyB7Ej255tpTaemk_-Ljmn4GcklldT14Hp4");
// use this to send message with payload data
Message message = new Message.Builder()
.collapseKey("message")
.timeToLive(3)
.delayWhileIdle(true)
.addData("message", "Welcome to Push Notifications") //you can get this message on client side app
.build();
//Use this code to send notification message to a single device
Result result = sender.send(message,
"APA91bEbKqwTbvvRuc24vAYljcrhslOw-jXBqozgH8C2OB3H8R7U00NbIf1xp151ptweX9VkZXyHMik022cNrEETm7eM0Z2JnFksWEw1niJ2sQfU3BjQGiGMq8KsaQ7E0jpz8YKJNbzkTYotLfmertE3K7RsJ1_hAA",
1);
System.out.println("Message Result: "+result.toString()); //Print message result on console
//Use this code to send notification message to multiple devices
ArrayList<String> devicesList = new ArrayList<String>();
//add your devices RegisterationID, one for each device
devicesList.add("APA91bEbKqwTbvvRuc24vAYljcrhslOw-jXBqozgH8C2OB3H8R7U00NbIf1xp151ptweX9VkZXyHMik022cNrEETm7eM0Z2JnFksWEw1niJ2sQfU3BjQGiGMq8KsaQ7E0jpz8YKJNbzkTYotLfmertE3K7RsJ1_hAA");
devicesList.add("APA91bEVcqKmPnESzgnGpEstHHymcpOwv52THv6u6u2Rl-PaMI4mU3Wkb9bZtuHp4NLs4snBl7aXXVkNn-IPEInGO2jEBnBI_oKEdrEoTo9BpY0i6a0QHeq8LDZd_XRzGRSv_R0rjzzZ1b6jXY60QqAI4P3PL79hMg");
//Use this code for multicast messages
MulticastResult multicastResult = sender.send(message, devicesList, 0);
System.out.println("Message Result: "+multicastResult.toString());//Print multicast message result on console
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
}`
The simplest way to implement GCM server side for Java is using restful POST.
URL: "https://android.googleapis.com/gcm/send"
Example code: using scribe framework as consumer
public void pushToAndroidDevice(String deviceToken, String data) {
OAuthRequest request = new OAuthRequest(Verb.POST, "https://android.googleapis.com/gcm/send");
request.addHeader("Authorization", "key=" + apiKey);
request.addHeader("Content-Type", "application/json");
request.addPayload(data);
Response response = request.send();
}
There are 2 ways you can implement server for GCM connections
1) XMPP
2) HTTP
The difference being XMPP allow you to get response back from device to server(Bidirectional) and HTTP is (Unidirectional) for GCM, you can only send push notification to device.
In case you need the full implementation of Java Client and HTTP server, here is the link
GCM Client and Server