-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
NIFI-5214 Added REST LookupService #2723
Conversation
This is cool, I will definitely make an aws web api version of this after it and my pr lands. |
InvokeHttp has a lot of options, so that might be worth splitting it into an initial merge and then an improvement that comes in 1.8. |
Don't I know it. If you want to do this as a 'series' of prs, I think you should document that in jira with tasks representing each PR and note it clearly in the pr. That will set the right context for the reviewers. I chose with my InvokeAwsGatewayApi processor to make sure that I replicated the InvokeHttp test suite. I would suggest that the end goal here would be to have and extensive set of tests adapted from that suite for the reader. |
@ottobackwards I grabbed a bunch of code from InvokeHttp and did a little copy pasta. Please take a look when you get a chance. I think moving the copy pasta into a shared utility package will need to be a separate ticket in order to keep the ticket scope down. |
@ottobackwards I think I might just go ahead and refactor that code now and get it done in this PR. |
@ottobackwards @ijokarumawak Added support for the new ProxyConfigurationService. Would appreciate a review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First couple of things
import java.util.Optional; | ||
import java.util.Set; | ||
import java.util.stream.Collectors; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be tagged HTTP?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably
if (proxyConfigurationService != null) { | ||
setProxy(builder); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why are we getting the SSL_CONTEXT_SERVICE twice?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copy pasta
@MikeThomsen public static final List<PropertyDescriptor> PROPERTIES = Collections.unmodifiableList(Arrays.asList(
PROP_METHOD,
PROP_URL,
PROP_SSL_CONTEXT_SERVICE,
PROP_CONNECT_TIMEOUT,
PROP_READ_TIMEOUT,
PROP_DATE_HEADER,
PROP_FOLLOW_REDIRECTS,
PROP_ATTRIBUTES_TO_SEND,
PROP_BASIC_AUTH_USERNAME,
PROP_BASIC_AUTH_PASSWORD,
PROP_PROXY_HOST,
PROP_PROXY_PORT,
PROP_PROXY_TYPE,
PROP_PROXY_USER,
PROP_PROXY_PASSWORD,
PROP_PUT_OUTPUT_IN_ATTRIBUTE,
PROP_PUT_ATTRIBUTE_MAX_LENGTH,
PROP_DIGEST_AUTH,
PROP_OUTPUT_RESPONSE_REGARDLESS,
PROP_TRUSTED_HOSTNAME,
PROP_ADD_HEADERS_TO_REQUEST,
PROP_CONTENT_TYPE,
PROP_SEND_BODY,
PROP_USE_CHUNKED_ENCODING,
PROP_PENALIZE_NO_RETRY)); Of these, I wonder if we should consider for the rest lookup For example: what if my lookup service is someone else's API and I need to send custom headers and api keys? |
I almost wonder if there should be an http rest connection service |
@ottobackwards I'll add user-defined headers and basic auth support, but that's it until there's a discussion around a HTTP Connection Service. I think that would be the place to go crazy on the options, not the lookup service. |
@MikeThomsen I agree. I haven't seen any conversation on it, but it seems more and more obvious, given the way things are going right? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MikeThomsen This new capability would be really useful. Thanks! I looked through the new lines of code and posted few comments. Please check those.
@@ -0,0 +1,42 @@ | |||
<!DOCTYPE html> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MikeThomsen additionalDetails.html should be in a directory src/main/resources/docs/qualifiedClassName/
not in docs.qualifiedClassName
. Please check additional docs can be accessed from component's usage page from NiFi UI.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
e -> e.getKey(), | ||
e -> e.getValue().toString() | ||
)); | ||
RecordReader reader = readerFactory.createRecordReader(variables, is, getLogger()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use try-with-resource statement, similar to what AbstractRouteRecord does:
try (final RecordReader reader = readerFactory.createRecordReader(originalAttributes, in, getLogger())) {
e -> e.getValue().toString() | ||
)); | ||
RecordReader reader = readerFactory.createRecordReader(variables, is, getLogger()); | ||
Record record = reader.nextRecord(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should handle the case where record is null.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, not sure how to trick it into doing null, but I put a check for that and made the optional be empty and added a new unit test to handle empty records (ex. badly defined schema has no fields from a rest response).
|
||
def coordinates = [ | ||
"schema.name": "simple", | ||
"endpoint": server.url + "/simple", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In real use-cases, do we expect user to set 'server.url' at a LookupRecord processor's user defined property?
As an alternative approach, I'd add endpoint url
at RestLookupService to define a template string to resolve target endpoint, and let callers such as LookupRecord to pass required variables to complete the target endpoint.
For example:
- At RestLookupService
- Endpoint URL:
https://some-api.example.com/buckets/${bucketId}/items/${itemId}
- Endpoint URL:
- At LookupRecord processor
- bucketId as user-defined-property: ./bucketId (a record path to get a value)
- itemId as user-defined-property: ./itemId (a record path to get a value)
By doing so, it also supports passing the entire endpoint URL:
- At RestLookupService
- Endpoint URL:
${endpoint}
- Endpoint URL:
- At LookupRecord processor
- endpoint as user-defined-property: a record path to construct an URL
I'd expect RestLookupService to have more control on REST related configurations, and callers just pass variables to make actual requests. How do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like that. How about this breakdown:
endpoint
is a template string set on a property descriptor and we use an EL compiler to generate the endpoint on the fly with those lookup coordinates.- Add
direct_endpoint
which is a treated as literal value to override that if present. - If both are present, throw an exception.
Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MikeThomsen I've replied to your question on the dev ML. I think endpoint
property can be used both cases where user want to use EL or just a literal endpoint. Because Query.prepare method can return the configured literal String if it does not contain any EL.
final Map<String, String> map = Collections.singletonMap("name", "John Smith");
PreparedQuery query = Query.prepare("${name}-${name:length()}");
String result = query.evaluateExpressions(map, null);
System.out.println(result);
query = Query.prepare("name-name:length()");
result = query.evaluateExpressions(map, null);
System.out.println(result);
final Map<String, String> map = Collections.singletonMap("name", "John Smith");
PreparedQuery query = Query.prepare("${name}-${name:length()}");
String result = query.evaluateExpressions(map, null);
System.out.println(result);
query = Query.prepare("name-name:length()");
result = query.evaluateExpressions(map, null);
System.out.println(result);
The code prints:
John Smith-10
name-name:length()
@ijokarumawak I can't believe I missed your comments. Will try to get to those today. |
@ijokarumawak Your first feedback points were merged in, let me know what you think of the last item and it should be easy to get done. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MikeThomsen this is a good idea - i think it will be extremely helpful for a lot of use cases. I left several inline comments. A couple I think are actually bugs (a 'fall through' case if RecordPath doesn't match and a threading issue). The others are mostly just regarding general NiFi coding conventions. Thanks!
.description("An optional record path that can be used to define where in a record to get the real data to merge " + | ||
"into the record set to be enriched. See documentation for examples of when this might be useful.") | ||
.expressionLanguageSupported(ExpressionLanguageScope.VARIABLE_REGISTRY) | ||
.addValidator(Validator.VALID) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should use a RecordPath validator, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. Didn't know it even existed :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
.description("The record reader to use for loading the payload and handling it as a record set.") | ||
.expressionLanguageSupported(ExpressionLanguageScope.NONE) | ||
.identifiesControllerService(RecordReaderFactory.class) | ||
.addValidator(Validator.VALID) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for the explicit validator when identifying controller services.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
getHeaders(context); | ||
} | ||
|
||
private Map<String, String> headers; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typical convention in NiFi is to add member variables at the top of the class before constructors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
private void getHeaders(ConfigurationContext context) { | ||
headers = new HashMap<>(); | ||
for (PropertyDescriptor descriptor : context.getProperties().keySet()) { | ||
if (descriptor.getName().startsWith("header.")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If dynamic properties are used for specifying headers to use, why not just let the property name be the name of the header? I.e., why require a "header." prefix? We should also document how dynamic properties are used via the @dynamicproperties annotation.
|
||
Record record = handleResponse(is, coordinates); | ||
|
||
return record != null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can just use Optional.ofNullable(record)
here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
} else if (value instanceof Map) { | ||
temp = new MapRecord(schema, (Map<String, Object>) value); | ||
} else { | ||
temp = new MapRecord(schema, new HashMap<String, Object>() {{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should avoid the creation of an anonymous inner class here and instead just create the MapRecord and then call put()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
FieldValue fieldValue = fv.get(); | ||
RecordSchema schema = new SimpleRecordSchema(Arrays.asList(fieldValue.getField())); | ||
String[] parts = recordPath.getPath().split("/"); | ||
String last = parts[parts.length - 1]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is actually what we want here. What if the path is something like /myField[./second/third = 'hello']
- in that case, last
will equal third = 'hello']
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great point. I think I'm going to have to make another property to handle this instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
return record; | ||
} catch (Exception ex) { | ||
is.close(); | ||
throw new RuntimeException(ex); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would avoid wrapping checked exceptions that are known to be thrown by method (such as IOException) in a RuntimeException. Am guessing you can just use throw ex
because all checked exceptions that can be caught are likely already declared for the method but not sure off the top of my head.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
return request.build(); | ||
} | ||
|
||
private String basicUser; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should avoid member variables throughout code and try to concentrate them at the top of the class. Additionally, these 3 member variables are not protected so the class is not thread-safe. Need to ensure that all ControllerServices are thread-safe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I fixed it.
|
||
@Override | ||
public Set<String> getRequiredKeys() { | ||
return new HashSet<String>(){{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should avoid the creation of anonymous inner classes when we can. We can also make this an UnmodifiableSet and just create a single instance of it as a member variable instead of constantly creating the Set
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@markap14 I think your concerns are addressed now. @ijokarumawak I tried out the expression language APIs and found that I can't just submit a Map, so I'm going to hold off on providing a dynamic template URL for now. That could be be a good improvement to add after the expression language compiler api gets updated to support generic maps as well (if it ever happens). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MikeThomsen Commented on the use of EL with a Map above, please check that out. Also, found the use of company.com, let's change it to example.com.
Do you think making the URL as template can be included in this PR? I will wait in case if you want to do that with this PR. Please let me know, once I got your confirmation, I will do the final review & testing before merging.
Thanks!
inner: [ | ||
"username": "jane.doe", | ||
"password": "testing7890", | ||
"email": "jane.doe@company.com" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The domain company.com
actually exists. Please change it to example.com
.
def record = response.get() | ||
Assert.assertEquals("jane.doe", record.getAsString("username")) | ||
Assert.assertEquals("testing7890", record.getAsString("password")) | ||
Assert.assertEquals("jane.doe@company.com", record.getAsString("email")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The domain company.com
actually exists. Please change it to example.com
.
@ijokarumawak I'll take a stab at adding the template url option. |
@@ -277,6 +280,20 @@ private void setProxy(OkHttpClient.Builder builder) { | |||
} | |||
} | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this handle AttributeExpressionLanguageParsingException? Is there any validation that can be done?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so because the EL support is enabled on the fly by the user, and any user who is enterprising enough to do that should be responsible for validating it. There's no good fallback option short of having them specify yet another key, and IMO that's overkill.
e -> e.getKey(), | ||
e -> e.getValue().toString() | ||
)); | ||
final PreparedQuery query = Query.prepare((String)coordinates.get(ENDPOINT_KEY)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MikeThomsen Thanks for adding this! This gets closer to what I was imagined. Excuse me if I was not clear enough, but from a user's stand point, I'd like to configure this ENDPOINT_KEY value at a RestLookupService property rather than passing it via coordinates.
The template it self can be configured at the service statically, but the variables to resolve a complete endpoint URL can be passed as coordinates.
Because, I think people would use one RestLookupService to lookup some endpoints (can be more than 1) of a particular Web service API. If they need to deal with different Web services, e.g. Twitter, Facebook and Salesforce ...etc they would use 3 RestLookupService, and each one can have the endpoint template defined.
Further more, if we implemented like that, we can cache the PreparedQuery instance without parsing the EL every time. Also, no need to have ENDPOINT_TEMPLATE_KEY
coordinate to tell if it contains EL or not because the PreparedQuery returns the original String if it does not contains EL.
How do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that would work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MikeThomsen I was able to use RestLookupService from LookupRecord processor. However, found several places for further improvements. Please check the comments. Thanks!
<version>1.7.0-SNAPSHOT</version> | ||
</parent> | ||
<modelVersion>4.0.0</modelVersion> | ||
<artifactId>nifi-standard-web-utils</artifactId> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think extracting TestServer.java was not necessary, and I'd avoid doing this in this PR. Because:
- It makes this PR to touch many files unnecessarily in terms of its objective.
- Having test scope dependency to
nifi-standard-processors
should be fine to reuse TestServer. - Even if we do extract TestServer, the module name
nifi-standard-web-utils
will not be appropriate to contain Test utility class. It should be named more clearly to state that this module is for test.nifi-mock
is a good example.
Do you have a strong motivation to do this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. I can see this functionality being reused in plenty of places and don't think we should tie the transitive dependencies of the standard package's test scope into other packages as that could complicate things down the road. I'll change the module name to nifi-standard-web-test-utils
. How about that?
.description("The username to be used by the client to authenticate against the Remote URL. Cannot include control characters (0-31), ':', or DEL (127).") | ||
.required(false) | ||
.addValidator(StandardValidators.createRegexMatchingValidator(Pattern.compile("^[\\x20-\\x39\\x3b-\\x7e\\x80-\\xff]+$"))) | ||
.build(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should support EL with Variable Registry.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
.required(false) | ||
.sensitive(true) | ||
.addValidator(StandardValidators.createRegexMatchingValidator(Pattern.compile("^[\\x20-\\x7e\\x80-\\xff]+$"))) | ||
.build(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should support EL with Variable Registry.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
// If the username/password properties are set then check if digest auth is being used | ||
if (!authUser.isEmpty() && isDigest) { | ||
final String authPass = trimToEmpty(context.getProperty(PROP_BASIC_AUTH_PASSWORD).getValue()); | ||
this.basicPass = authPass; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this.basicPass
should be set regardless of isDigest
, similar to this.basicUser
. Having it here will leave basicPass unset if isDigest is false. Is this correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
public Optional<Record> lookup(Map<String, Object> coordinates) throws LookupFailureException { | ||
final String endpoint = determineEndpoint(coordinates); | ||
final String mimeType = (String)coordinates.get(MIME_TYPE_KEY); | ||
final String method = (String)coordinates.get(METHOD_KEY); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using getOrDefault(METHOD_KEY, "get")
would be more user friendly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A value is required because it's a required key, so I don't think that's necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I used the lookup service from LookupRecord, I felt passing a literal value as RecordPath was a bit tricky. I had to use concat('get')
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MikeThomsen RequiredKeys is not mandatory, it can be an empty Set. Also, this is a Lookup
service. Get should be the default method like the famous cURL command.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can see how that would be awkward. Do you want to handle that with a combination of property + lookup key (latter overriding the former) or defer to fixing LookupRecord
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want RestLookupService to have 'get' as default value. It will cover 80% of use-cases I assume. I will send a PR to your PR and if it looks fine, then I'd give my +1 so that you can merge.
<p>Headers are supported using dynamic properties. Just add a dynamic property and the name will be the header name and the value will be the value for the header. Expression language | ||
powered by input from the variable registry is supported.</p> | ||
<h2>Dynamic URLs</h2> | ||
<p>The URL property supports expression language in a non-standard way: through the lookup key/value pairs configured on the processor. The configuration specified by the user will be passed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd omit the expression, 'in a non-standard way', because I envision this pattern will be used more frequently and I'm planning to add copy FlowFile attribute to coordinates capability at LookupRecord processor
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@DynamicProperty(name = "*", value = "*", description = "All dynamic properties are added as HTTP headers with the name " + | ||
"as the header name and the value as the header value.") | ||
}) | ||
public class RestLookupService extends AbstractControllerService implements LookupService<Record> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
implements RecordLookupService
is better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
Set<String> _keys = new HashSet<>(); | ||
_keys.add(MIME_TYPE_KEY); | ||
_keys.add(METHOD_KEY); | ||
KEYS = Collections.unmodifiableSet(_keys); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MIME_TYPE is only used for put and post requests and should be optional. Also if we support 'get' is the default method, then this controller service does not have any required KEYS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a fair point, and it needs validation on the verbs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
Other than the issue w/ literals you cited, looks ready to merge IMO. |
@ijokarumawak merged your PR. You can merge at any time. |
@ijokarumawak the AWS gateway tests are broken because I moved test server. I'll do a patch for that. |
@ijokarumawak I accidentally squashed the changes that @markap14 requested into your commit. |
@MikeThomsen that's ok, I think it's time to squash all commits into one, I'm fine to lose my commit histories. Now we have #2777 merged into master, so we can utilize it. Can you update RestLookupService to do that? Like I did it locally here. I am +1 once this gets done. |
1. Added 'Base URL' property to address environment specific part of URL. 2. Removed 'Record Path Property Name' property, because the name of a resulted record field of a record path can be obtained by field name. 3. Lower cased HTTP method name should be used throughout. 4. Added mimeType require check when body is specified. 5. Added debug log to print HTTP response code. 6. Prepare for NIFI-5287. 7. Fixed that mime.type being used regardless of whether body is specified or not, caused NullPointerException when 'mime.type' is not specified when it is not required. 8. Updated documentation.
- Use PropertyValue instead of PreparedQuery to utilize Variable Registry. - Removed BASE_URL because Variable Registry can be used at URL
@ijokarumawak Added a new commit that should get it to close out. |
@MikeThomsen I squashed all commits into one, did manual test. LGTM, +1. merging to master! Thank you! |
Thank you for submitting a contribution to Apache NiFi.
In order to streamline the review of the contribution we ask you
to ensure the following steps have been taken:
For all changes:
Is there a JIRA ticket associated with this PR? Is it referenced
in the commit message?
Does your PR title start with NIFI-XXXX where XXXX is the JIRA number you are trying to resolve? Pay particular attention to the hyphen "-" character.
Has your PR been rebased against the latest commit within the target branch (typically master)?
Is your initial contribution a single, squashed commit?
For code changes:
For documentation related changes:
Note:
Please ensure that once the PR is submitted, you check travis-ci for build issues and submit an update to your PR as soon as possible.