I am writing a Java Azure program that builds an envelope and logs the data to Application Insights in the Azure Portal. Currently it is tailored to just accept an HTTP Request function, but I am trying to make it compatible with using the Azure Service Bus and other services as well. My cf.buildEnvelope() function currently is structed as: buildEnvelope(HttpRequestMessage<Optional> req, ExecutionContext context)
and I want to change it to:
buildEnvelope(String req, ExecutionContext context) so that I can use it for more than just the HTTP Request. In order to do this I believe I just need to convert my req to a normal String in my Handler, but I cannot figure out how to do so.
Here is my Handler:
`import com.microsoft.azure.functions.ExecutionContext;
import com.microsoft.azure.functions.HttpMethod;
import com.microsoft.azure.functions.HttpRequestMessage;
import com.microsoft.azure.functions.HttpResponseMessage;
import com.microsoft.azure.functions.HttpStatus;
import com.microsoft.azure.functions.annotation.AuthorizationLevel;
import com.microsoft.azure.functions.annotation.FunctionName;
import com.microsoft.azure.functions.annotation.HttpTrigger;
//import com.microsoft.azure.functions.annotation.ServiceBusQueueTrigger;
import java.util.Optional;
public class Handler {
#FunctionName("HttpHandler")
public HttpResponseMessage runHttp(
#HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS)
HttpRequestMessage<Optional<String>> req, ExecutionContext context) {
Optional<String> payload = req.getBody();
// payload to String (Ignore this)
//String payload2 = req.getBody().orElse("");
MessageEnvelope envelope = cf.buildEnvelope(req, context);
cf.log(envelope, context, payload);
return req.createResponseBuilder(HttpStatus.OK).body("Data logged").build();
}
}`
And my buildEnvelope function:
`public static MessageEnvelope buildEnvelope(HttpRequestMessage<Optional<String>> req, ExecutionContext context) {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
Optional<String> payload = req.getBody();
MessageEnvelope msgEnvelope = new MessageEnvelope(req, context);
msgEnvelope.setMessageMetadata(new MessageMetadata(System.getenv("AZURE_FUNCTIONS_ENVIRONMENT"), System.getenv("USERDNSDOMAIN")));
msgEnvelope.setMessageAttributes(new MessageAttributes("", ""));
msgEnvelope.setMessageParameters(new MessageParameters("", ""));
msgEnvelope.setMessageVariable(new String[]{"0", "1", "2"});
msgEnvelope.setMessagePayload(gson.toJson(payload));
return msgEnvelope;
}`
Please help me make this conversion. Thank you!
According to the JavaDocs
You should be able to call String payload2 = req.getBody().get(); to get the string value out of the Optional<String> class
Related
I'm using Retrofit 1.9.0. I have the following interface:
import retrofit.http.PUT;
public interface ShovelsApi {
#PUT("/api/parameters/shovel/{vhost}/{name}")
RetrievedShovel putShovel(#Path("vhost") final String vhost, #Path("name") final String name, #Body final Component<Shovel> shovel);
}
However, when this fails, all I get back is a null RetrievedShovel :
final Shovel shovel = new Shovel();
shovel.setSrcDeleteAfter(1);
shovel.setAckMode("on-confirm");
shovel.setSrcQueue("srcq");
shovel.setSrcUri("amqp://");
shovel.setDestQueue("destq");
shovel.setDestUri("amqp://");
final RetrievedShovel retrievedShovel = shovelsApi.getApi().putShovel(
"/", "myshovel", new Component<>("shovel", "myshovel", shovel));
System.out.println(retrievedShovel); // null
How do I get back the HTTP response, to inspect the status code and message etc?
Thanks!
I am trying to return data in JSON format from a Google Cloud Function written in Java. I have an example from GoogleCloudPlatform/java-doc-samples (https://github.com/GoogleCloudPlatform/java-docs-samples/blob/main/functions/http/http-method/src/main/java/functions/HttpMethod.java) that shows me how to handle different types of HTTP methods, but it doesn't show how to write JSON in the response.
What I'd like to do would be something like the following:
import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;
import static java.util.Map.entry;
public class ReturnJSON implements HttpFunction {
#Override
public void service(HttpRequest request, HttpResponse response)
throws IOException {
Map<String, String> returnJSON = Map.ofEntries(
entry("name", "Test User"),
entry("email", "test_user#example.com"),
entry("emotion", "happy")
);
var writer = new PrintWriter(response.getWriter());
writer.write(returnJSON);
}
The end goal of this would be to send an HTTP request to the ReturnJSON function (deployed as a cloud function at a certain URL) that returns JSON when I use JavaScript to fetch it:
fetch("https://example.com/return-json", { method: "GET" })
.then(response => response.json())
.then(data => console.log(data));
You have to write your json as a string and to add the content type in the response, like that
response.setContentType("application/json");
JSON is just a string but you are better off building JSON using a Java library in this case. there are few options
https://github.com/stleary/JSON-java
http://json-lib.sourceforge.net/
I have created the next savePartner() method inside PartnerController class like this:
public void savePartner(View partnerForm) {
context = partnerForm.getContext();
PartnerDto partner = createPartner(partnerForm);
String jsonPartner = convert(partner);
Call<String> call = appAPI.savePartner("application/json", jsonPartner);
Log.i(TAG, "getPartners submitted to API.");
call.enqueue(new Callback<String>() {
#Override
public void onResponse(Call<String> call, Response<String> response) {
if(response.isSuccessful()) {
String responseCall = response.body();
} else {
System.out.println(response.errorBody());
}
}
#Override
public void onFailure(Call<String> call, Throwable t) {
TableRow rowHeader = new TableRow(context);
TextView name = new TextView(context);
name.setText(t.getMessage());
rowHeader.addView(name);
//partnerForm.addView(rowHeader);
t.printStackTrace();
}
});
}
And I have added the method savePartner to retrofit interface:
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.Headers;
import retrofit2.http.POST;
public interface IApplicationApi {
#GET("Partner/")
//Call<List<PartnerDto>> loadPartners(#Header("Authorization") String authorization);
Call<List<PartnerDto>> loadPartners();
#POST("Partner/")
Call<String> savePartner(#Header("Content-Type") String content_type, #Body String partner);
}
When I execute the post call in postman works (code 200), but I debugged the previous in android-studio and I obtain the next error:
Response{protocol=http/1.1, code=400, message=Bad Request, url=https://localhost/Partner/}
And I can't obtain more info about the error. The request is the next:
Request{method=POST, url=https://localhost/Partner/, tags={class retrofit2.Invocation=administracion.MyProject.APIService.IApplicationApi.savePartner() [application/json, {"email":null,"id":4,"lastname":null,"name":"me","phonenumber":0,"productamount":0.0,"productquantity":0.0,"registereddate":"2021-02-10T00:00:00"}]}}
I put these values on postman, and it works like a charm. I don't know why this request is bad. Could someone give me some clue?
Thanks in advance for the help! ^^
Updated 01/03/2021
I can get the cause of the error using httplogginginterceptor, I share this in case someone more need it :)
https://howtodoinjava.com/retrofit2/logging-with-retrofit2/
you can use HttpLoggingInterceptor and log your request. I hope you are missing a field in your json body or request body
try replacing this #POST("Partner/")
with #POST("Partner")
I did not find any example of how to replace the deprecation method.
The examples on the okhttp3 main page are old.
This is one of them:
public static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
If someone could solve it, I would appreciate your help.
Update:
I'm using 'com.squareup.okhttp3:okhttp:4.0.1'
Java Solution:
Use create(String, MediaType) instead of create(MediaType, String) for example
Kotlin Solution:
Use the extension function content.toRequestBody(contentType);
for the File type file.asRequestBody(contentType)
Note:
I'm using kotlin, but my IDE just doesn't automatically import the class or method like import okhttp3.RequestBody.Companion.toRequestBody, so I import it manually...then use it as the example given by Saeed Younus and Pratyesh below
For more: The documentation
(In Android Studio or any Jetbrain's IDE, the solution to the deprecated methods or class can be found by just holding the Ctrl and clicking on the create(...) of RequestBody.create)
In com.squareup.okhttp3:okhttp:4.1.0
MediaType.get("application/json; charset=utf-8") no more available.
instead this we need to use "application/json; charset=utf-8".toMediaTypeOrNull().
For example how we need to create request body now since okhttp:4.1.0
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.toRequestBody
val jsonObject = JSONObject()
jsonObject.put("name", "Ancd test")
jsonObject.put("city", "delhi")
jsonObject.put("age", "23")
val body = jsonObject.toString().toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
To those wondering where the answers are coming from!
All the alternatives/solutions(as described by the answer) are documented in the corresponding deprecated code! Just manoeuvre to it (the deprecated code) using whichever means your IDE supports. For example, to see the alternative/solution to the deprecated code RequestBody.create(...,...) when using AndroidStudio or any Jetbrain's IDE, just long-press Ctrl and hover over the RequestBody.create(...,...) then click on it when it's hovered over successfully
You need to import these files manually may be this is a bug in android studio. It is not suggested but this is work for Okhttp 4.2.2
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.asRequestBody
and use as
val file = File("path")
file.asRequestBody("image/jpeg".toMediaTypeOrNull())
It was deprecated since version 4.0.0 of okhttp3.
The documentation for that version says
#JvmStatic
#Deprecated(
message = "Moved to extension function. Put the 'content' argument first to fix Java",
replaceWith = ReplaceWith(
expression = "content.toRequestBody(contentType)",
imports = ["okhttp3.RequestBody.Companion.toRequestBody"]
),
level = DeprecationLevel.WARNING)
fun create(contentType: MediaType?, content: String) = content.toRequestBody(contentType)
I haven't tried it but I believe that you should be good by doing the following:
package com.example;
import java.io.IOException;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class Test {
public static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
public static void main(String[] args) {
}
String post(String url, String json) throws IOException {
//RequestBody body = RequestBody.create(JSON, json);
RequestBody body = RequestBody.Companion.create(json, JSON);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
}
Update: I tried to compile the file shown above using the following dependency version and RequestBody.Companion.create(json, JSON) doesn't seem to be deprecated.
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.0.0</version>
</dependency>
Can u update like that
val apiRequest = RequestBody.create(MediaType.parse("text/plain;charset=utf-8"), "edit_group")
val tokenRequest = RequestBody.create(MediaType.parse("text/plain;charset=utf-8"), token)
val fileReqBody = RequestBody.create(MediaType.parse("image/*"), file)
to
val apiRequest = "edit_group".toRequestBody("text/plain;charset=utf-8".toMediaType())
val tokenRequest = token.toRequestBody("text/plain;charset=utf-8".toMediaType())
val file = File(path)
val fileReqBody = file.asRequestBody("image/*".toMediaType())
Just change ResponseBody.create(MediaType.parse("text/json"), plainBody.trim())
to ResponseBody.create(plainBody.trim(),MediaType.parse("text/json"))
Just had a quick look at the documentation . It reads deprecated, however the alternative is provided in the doc.
json.toRequestBody(contentType) should do the trick for you.
Below is the documentation link:
https://github.com/square/okhttp/blob/master/okhttp/src/main/java/okhttp3/RequestBody.kt
public static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
RequestBody body = RequestBody.Companion.create(json, JSON)
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
ok according to okhttp 4 many thing updated as official docs
RequestBody.create() is upgraded to File.asRequestBody()
You Just Need To Flip Your Args.
#kotlin.jvm.JvmStatic #kotlin.Deprecated public final fun create(contentType: okhttp3.MediaType?, file: java.io.File): okhttp3.RequestBody { }
#kotlin.jvm.JvmStatic #kotlin.Deprecated #kotlin.jvm.JvmOverloads public final fun create(contentType: okhttp3.MediaType?, content: kotlin.ByteArray, offset: kotlin.Int , byteCount: kotlin.Int): okhttp3.RequestBody {}
#kotlin.jvm.JvmStatic #kotlin.Deprecated public final fun create(contentType: okhttp3.MediaType?, content: kotlin.String): okhttp3.RequestBody {}
#kotlin.jvm.JvmStatic #kotlin.Deprecated public final fun create(contentType: okhttp3.MediaType?, content: okio.ByteString): okhttp3.RequestBody { }
We are moving from Java 8 to Java 11, and thus, from Spring Boot 1.5.6 to 2.1.2. We noticed, that when using RestTemplate, the '+' sign is not encoded to '%2B' anymore (changes by SPR-14828). This would be okay, because RFC3986 doesn't list '+' as a reserved character, but it is still interpreted as a ' ' (space) when received in a Spring Boot endpoint.
We have a search query which can take optional timestamps as query parameters. The query looks something like http://example.com/search?beforeTimestamp=2019-01-21T14:56:50%2B00:00.
We can't figure out how to send an encoded plus sign, without it being double-encoded. Query parameter 2019-01-21T14:56:50+00:00 would be interpreted as 2019-01-21T14:56:50 00:00. If we were to encode the parameter ourselves (2019-01-21T14:56:50%2B00:00), then it would be received and interpreted as 2019-01-21T14:56:50%252B00:00.
An additional constraint is, that we want to set the base url elsewhere, when setting up the restTemplate, not where the query is being executed.
Alternatively, is there a way to force '+' not to be interpreted as ' ' by the endpoint?
I have written a short example demonstrating some ways of achieving stricter encoding with their drawbacks explained as comments:
package com.example.clientandserver;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriUtils;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
#SpringBootApplication
#RestController
public class ClientAndServerApp implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(ClientAndServerApp.class, args);
}
#Override
public void run(String... args) {
String beforeTimestamp = "2019-01-21T14:56:50+00:00";
// Previously - base url and raw params (encoded automatically).
// This worked in the earlier version of Spring Boot
{
RestTemplate restTemplate = new RestTemplateBuilder()
.rootUri("http://localhost:8080").build();
UriComponentsBuilder b = UriComponentsBuilder.fromPath("/search");
if (beforeTimestamp != null) {
b.queryParam("beforeTimestamp", beforeTimestamp);
}
restTemplate.getForEntity(b.toUriString(), Object.class);
// Received: 2019-01-21T14:56:50 00:00
// Plus sign missing here ^
}
// Option 1 - no base url and encoding the param ourselves.
{
RestTemplate restTemplate = new RestTemplate();
UriComponentsBuilder b = UriComponentsBuilder
.fromHttpUrl("http://localhost:8080/search");
if (beforeTimestamp != null) {
b.queryParam(
"beforeTimestamp",
UriUtils.encode(beforeTimestamp, StandardCharsets.UTF_8)
);
}
restTemplate.getForEntity(
b.build(true).toUri(), Object.class
).getBody();
// Received: 2019-01-21T14:56:50+00:00
}
// Option 2 - with templated base url, query parameter is not optional.
{
RestTemplate restTemplate = new RestTemplateBuilder()
.rootUri("http://localhost:8080")
.uriTemplateHandler(new DefaultUriBuilderFactory())
.build();
Map<String, String> params = new HashMap<>();
params.put("beforeTimestamp", beforeTimestamp);
restTemplate.getForEntity(
"/search?beforeTimestamp={beforeTimestamp}",
Object.class,
params);
// Received: 2019-01-21T14:56:50+00:00
}
}
#GetMapping("/search")
public void search(#RequestParam String beforeTimestamp) {
System.out.println("Received: " + beforeTimestamp);
}
}
We realized the URL can be modified in an interceptor after the encoding is done. So a solution would be to use an interceptor, that encodes the plus sign in the query params.
RestTemplate restTemplate = new RestTemplateBuilder()
.rootUri("http://localhost:8080")
.interceptors(new PlusEncoderInterceptor())
.build();
A shortened example:
public class PlusEncoderInterceptor implements ClientHttpRequestInterceptor {
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
return execution.execute(new HttpRequestWrapper(request) {
#Override
public URI getURI() {
URI u = super.getURI();
String strictlyEscapedQuery = StringUtils.replace(u.getRawQuery(), "+", "%2B");
return UriComponentsBuilder.fromUri(u)
.replaceQuery(strictlyEscapedQuery)
.build(true).toUri();
}
}, body);
}
}
The issue has been discussed here as well.
Encoding of URI Variables on RestTemplate [SPR-16202]
A simpler solution is to set the encoding mode on the URI builder to VALUES_ONLY.
DefaultUriBuilderFactory builderFactory = new DefaultUriBuilderFactory();
builderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY);
RestTemplate restTemplate = new RestTemplateBuilder()
.rootUri("http://localhost:8080")
.uriTemplateHandler(builderFactory)
.build();
This achieved the same result as using the PlusEncodingInterceptor when using query parameters.
Thanks https://stackoverflow.com/users/4466695/gregor-eesmaa, it solved my issue. Just wanted to add that in case if you can format URL before calling RestTemplate, you can fix the URL at once (instead of replacing it in PlusEncoderInterceptor):
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString("/search");
uriBuilder.queryParam("beforeTimestamp", "2019-01-21T14:56:50+00:00");
URI uriPlus = uriBuilder.encode().build(false).toUri();
// import org.springframework.util.StringUtils;
String strictlyEscapedQuery = StringUtils.replace(uriPlus.getRawQuery(), "+", "%2B");
URI uri = UriComponentsBuilder.fromUri(uriPlus)
.replaceQuery(strictlyEscapedQuery)
.build(true).toUri();
// prints "/search?beforeTimestamp=2019-01-21T14:56:50%2B00:00"
System.out.println(uri);
Then you can use in RestTemplate call:
RequestEntity<?> requestEntity = RequestEntity.get(uri).build();
ResponseEntity<String> responseEntity = restTemplate.exchange(requestEntity, String.class);
To get around this kind of issue, I found it easier to build the URI by hand.
URI uri = new URI(siteProperties.getBaseUrl()
+ "v3/elements/"
+ URLEncoder.encode("user/" + user + "/type/" + type, UTF_8)
+ "/"
+ URLEncoder.encode(id, UTF_8)
);
restTemplate.exchange(uri, DELETE, new HttpEntity<>(httpHeaders), Void.class);
Just want to add on!, You can try with this without configure every where u used RestTemplate in your project, config also support UTF-8 encoding respons:
public class PlusEncoderInterceptor implements ClientHttpRequestInterceptor {
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
return execution.execute(new HttpRequestWrapper(request) {
#NotNull
#Override
public URI getURI() {
URI u = super.getURI();
String strictlyEscapedQuery = StringUtils.replace(u.getRawQuery(), "+", "%2B");
return UriComponentsBuilder.fromUri(u).replaceQuery(strictlyEscapedQuery).build(true).toUri();
}
}, body);
}
}
#Configuration
public class RestTemplateConfig {
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplateBuilder().interceptors(new PlusEncoderInterceptor()).build();
restTemplate.getMessageConverters().add(0, new StringHttpMessageConverter(StandardCharsets.UTF_8));
return restTemplate;
}
}