Nifi之ExecuteScript使用方法 (二)

本文是一系列文章中的第二篇,描述了如何使用ExecuteScript完成某些任务的各种"方法".

本文介绍了如何使用NiFi处理器ExecuteScript完成某些任务的各种"recipes",以及 Groovy,Jython,Javascript(Nashorn)和JRuby中给出的示例。这是本系列的第2部分,我将讨论读取和写入流文件内容以及错误处理。

第1部分- NiFi API和FlowFiles简介

  • 从传入队列获取流文件
  • 创建新的流文件
  • 使用流文件属性
  • 传输流文件
  • 记录

第2部分 - FlowFile I / O和错误处理

  • 从流文件中读取
  • 写入流文件
  • 读取和写入流文件
  • 错误处理

第3部分- 高级功能

  • 使用动态属性
  • 添加模块
  • 国家管理
  • 访问控制器服务

FlowFile I / O简介

NiFi中的流文件由两个主要组件构成,即属性和内容。属性是关于内容/流文件的元数据,我们在本系列的第一部分看到了如何使用ExecuteScript来操作它们.

流文件的内容本质上只是一个字节集合,没有固有的结构,模式,格式等。各种NiFi处理器假设传入的流文件具有特定的模式/格式(或者从属性中确定它作为“mime.type”或以其他方式推断它)。然后,这些处理器可以基于文件确实具有该格式的假设来对内容起作用(并且如果它们不这样,则经常转移到“Failure”关系队列)。处理器也可以输出指定格式的流文件,这在处理器中有描述。NiFi文档。

流文件内容的输入和输出(I/O)通过ProcessSession API提供(有关更多信息,请参阅第1部分)同时对外开放了"session 变量"以供调用。我们可以通过调用session.read()获得FlowFile的输入流对象或调用session.write()获得FlowFile的输出流对象.这两个方法会调用相应的回调接口和回调函数,并返回InputStream和/或OutputStream引用以供回调使用。有三个主要的回调接口,每个接口都有自己的用例:

InputStreamCallback

session.read(flowFileinputStreamCallback)方法会调用输入流回调接口,该方法会返回一个从FlowFile中读取到数据的输入流.这个回调函数只有一个方法

void process(InputStream in) throws IOException

这个接口提供了一个managed输入流供调用者使用,这个流会自动的打开和关闭,也支持手动关闭.如果您只是从特定的流文件中读取数据,而不是将数据写回该文件,那么您将使用这种办法.

比如我们想做的事情是接收到一条流数据,将其切分成多条输出出去.这是一个很常用也很简单的场景,此时我们就可以使用上面的办法.

OutputStreamCallback

输出流回调接口在调用session.write(FlowFile,outputStreamCallback)的时候被调用,它会返回一个向FlowFile中写数据的OutputSteam.这个接口也只有一个方法

void process(OutputStream out) throws IOException

此接口提供输出流以供使用。尽管可以手动关闭流,但输出流会自动打开和关闭 - 如果包含这些流的任何流打开应该清除的资源,这一点就变得非常重要了。

使用方法 : 比如ExecuteScript将从内部或外部文件生成数据,但不生成流文件。然后你将使用session.create()创建一个新的FlowFile,再使用session.write(flowFileoutputStreamCallback)来插入内容。

StreamCallback

当调用session.write(FlowFile,StreamCallback)的时候,会返回InputStream和OutputStream,从中读写流文件的内容.接口方法如下:

void process (InputStream in ,OutputStream out )抛出IOException 

同上边的接口相同,改接口也会自动关闭流也支持手动关闭.

使用场景 : 当您想要处理传入的流文件并用新的东西覆盖其内容时,例如 EncryptContent处理器。

由于这些回调是Java对象,因此脚本必须创建并将其传递给会话方法,这些recipes将针对各种脚本语言进行说明。其他读取和写入流文件的方法:

  • 使用session.read(flowFile)返回一个InputStream。这减轻了对InputStreamCallback的需求,而是返回可以读取的InputStream。作为交换,您必须手动管理(关闭,例如)InputStream。
  • 使用session.importFrom(inputStreamflowFile)从InputStream写入FlowFile。这取代了传递了OutputStreamCallback的session.write()的需要。

代码样例

应用场景1 :使用回调读取传入流文件的内容,您有连接到ExecuteScript的传入连接,并希望从队列中检索流文件的内容以进行处理。

方案:使用来自session对象的方法read(flowFileinputStreamCallback)。read()方法需要传入一个InputStreamCallback对象。请注意,因为InputStreamCallback是一个对象,所以默认情况下内容只对该对象可见。如果需要使用read()方法之外的数据,请使用全局范围的变量。这些示例将传入流文件的完整内容存储到String中(使用Apache Commons的IOUtils类)。注意:对于大流量文件,这不是最好的技术; 相反,您应该只读取您需要的数据,并根据需要进行处理。对于像SplitText这样的东西,你可以一次读取一行并在InputStreamCallback中处理它,或者使用前面提到的session.read(flowFile)方法来获得在回调之外使用的InputStream引用。

Groovy

import org.apache.commons.io.IOUtils
import java.nio.charset.StandardCharsets
 
flowFile = session.get()
if(!flowFile)return
def text = ''
// Cast a closure with an inputStream parameter to InputStreamCallback
session.read(flowFile, {inputStream ->
  text = IOUtils.toString(inputStream, StandardCharsets.UTF_8)
  // Do something with text here
} as InputStreamCallback)

Jython

from org.apache.commons.io import IOUtils
from java.nio.charset import StandardCharsets
from org.apache.nifi.processor.io import InputStreamCallback
 
# Define a subclass of InputStreamCallback for use in session.read()
class PyInputStreamCallback(InputStreamCallback):
  def __init__(self):
        pass
  def process(self, inputStream):
    text = IOUtils.toString(inputStream, StandardCharsets.UTF_8)
    # Do something with text here
# end class
flowFile = session.get()
if(flowFile != None):
    session.read(flowFile, PyInputStreamCallback())
# implicit return at the end

 JavaScript

var InputStreamCallback =  Java.type("org.apache.nifi.processor.io.InputStreamCallback")
var IOUtils = Java.type("org.apache.commons.io.IOUtils")
var StandardCharsets = Java.type("java.nio.charset.StandardCharsets")
 
var flowFile = session.get();
if(flowFile != null) {
  // Create a new InputStreamCallback, passing in a function to define the interface method
  session.read(flowFile,
    new InputStreamCallback(function(inputStream) {
        var text = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
        // Do something with text here
    }));
}

JRuby

java_import org.apache.commons.io.IOUtils
java_import org.apache.nifi.processor.io.InputStreamCallback
 
# Define a subclass of InputStreamCallback for use in session.read()
class JRubyInputStreamCallback
  include InputStreamCallback
  def process(inputStream)
    text = IOUtils.toString(inputStream)
    # Do something with text here
  end
end
jrubyInputStreamCallback = JRubyInputStreamCallback.new
flowFile = session.get()
if flowFile != nil
  session.read(flowFile, jrubyInputStreamCallback)
end

 

应用场景2 : 使用回调函数将内容写入传出流文件,我们希望对传入的流文件除了读取内容,还要填充新的内容.

方案 : 使用会话对象中的write(flowFileoutputStreamCallback)方法。传递给write()方法需要一个OutputStreamCallback对象。请注意,因为OutputStreamCallback是一个对象,所以默认情况下内容只对该对象可见。如果需要使用write()方法之外的数据,请使用更全局范围的变量。这些示例将示例String写入flowFile。

Groovy

import org.apache.commons.io.IOUtils
import java.nio.charset.StandardCharsets
 
flowFile = session.get()
if(!flowFile) return
def text = 'Hello world!'
// Cast a closure with an outputStream parameter to OutputStreamCallback
flowFile = session.write(flowFile, {outputStream ->
  outputStream.write(text.getBytes(StandardCharsets.UTF_8))
} as OutputStreamCallback)

Jython

from org.apache.commons.io import IOUtils
from java.nio.charset import StandardCharsets
from org.apache.nifi.processor.io import OutputStreamCallback
 
# Define a subclass of OutputStreamCallback for use in session.write()
class PyOutputStreamCallback(OutputStreamCallback):
  def __init__(self):
        pass
  def process(self, outputStream):
    outputStream.write(bytearray('Hello World!'.encode('utf-8')))
# end class
flowFile = session.get()
if(flowFile != None):
    flowFile = session.write(flowFile, PyOutputStreamCallback())
# implicit return at the end

JavaScript

var OutputStreamCallback =  Java.type("org.apache.nifi.processor.io.OutputStreamCallback");
var IOUtils = Java.type("org.apache.commons.io.IOUtils");
var StandardCharsets = Java.type("java.nio.charset.StandardCharsets");
 
var flowFile = session.get();
if(flowFile != null) {
  // Create a new OutputStreamCallback, passing in a function to define the interface method
  flowFile = session.write(flowFile,
    new OutputStreamCallback(function(outputStream) {
        outputStream.write("Hello World!".getBytes(StandardCharsets.UTF_8))
    }));
}

JRuby

java_import org.apache.commons.io.IOUtils
java_import java.nio.charset.StandardCharsets
java_import org.apache.nifi.processor.io.OutputStreamCallback
 
# Define a subclass of OutputStreamCallback for use in session.write()
class JRubyOutputStreamCallback
  include OutputStreamCallback
  def process(outputStream)
    outputStream.write("Hello World!".to_java.getBytes(StandardCharsets::UTF_8))
  end
end
jrubyOutputStreamCallback = JRubyOutputStreamCallback.new
flowFile = session.get()
if flowFile != nil
  flowFile = session.write(flowFile, jrubyOutputStreamCallback)
end

 

应用场景3 : 使用回调覆盖带有更新内容的传入流文件,您希望重用传入的流文件,但希望修改其传出流文件的内容。

方案 : 使用来自session对象的write(flowFilestreamCallback)方法。传递给write()方法需要StreamCallback对象。StreamCallback提供InputStream(来自传入流文件)和outputStream(用于该流文件的下一个版本),因此您可以使用InputStream获取流文件的当前内容,然后修改它们并将它们写回到流文件。这会覆盖流文件的内容,因此对于追加,您必须通过附加读入内容来处理它,或者使用不同的方法(使用session.append()而不是session.write())。请注意,由于StreamCallback是一个对象,因此默认情况下内容仅对该对象可见。如果需要使用write()方法之外的数据,请使用更全局范围的变量。

Groovy

import org.apache.commons.io.IOUtils
import java.nio.charset.StandardCharsets
 
flowFile = session.get()
if(!flowFile) return
def text = 'Hello world!'
// Cast a closure with an inputStream and outputStream parameter to StreamCallback
flowFile = session.write(flowFile, {inputStream, outputStream ->
  text = IOUtils.toString(inputStream, StandardCharsets.UTF_8)
  outputStream.write(text.reverse().getBytes(StandardCharsets.UTF_8))
} as StreamCallback)
session.transfer(flowFile, REL_SUCCESS)

Jython

from org.apache.commons.io import IOUtils
from java.nio.charset import StandardCharsets
from org.apache.nifi.processor.io import StreamCallback
 
# Define a subclass of StreamCallback for use in session.write()
class PyStreamCallback(StreamCallback):
  def __init__(self):
        pass
  def process(self, inputStream, outputStream):
    text = IOUtils.toString(inputStream, StandardCharsets.UTF_8)
    outputStream.write(bytearray('Hello World!'[::-1].encode('utf-8')))
# end class
flowFile = session.get()
if(flowFile != None):
    flowFile = session.write(flowFile, PyStreamCallback())
# implicit return at the end

JavaScript

var StreamCallback =  Java.type("org.apache.nifi.processor.io.StreamCallback");
var IOUtils = Java.type("org.apache.commons.io.IOUtils");
var StandardCharsets = Java.type("java.nio.charset.StandardCharsets");
 
var flowFile = session.get();
if(flowFile != null) {
  // Create a new StreamCallback, passing in a function to define the interface method
  flowFile = session.write(flowFile,
    new StreamCallback(function(inputStream, outputStream) {
        var text = IOUtils.toString(inputStream, StandardCharsets.UTF_8)
        outputStream.write(text.split("").reverse().join("").getBytes(StandardCharsets.UTF_8))
    }));
}

JRuby

java_import org.apache.commons.io.IOUtils
java_import java.nio.charset.StandardCharsets
java_import org.apache.nifi.processor.io.StreamCallback
 
# Define a subclass of StreamCallback for use in session.write()
class JRubyStreamCallback
  include StreamCallback
  def process(inputStream, outputStream)
    text = IOUtils.toString(inputStream)
    outputStream.write((text.reverse!).to_java.getBytes(StandardCharsets::UTF_8))
  end
end
jrubyStreamCallback = JRubyStreamCallback.new
flowFile = session.get()
if flowFile != nil
  flowFile = session.write(flowFile, jrubyStreamCallback)
end

 

应用场景4 : 处理脚本处理过程中的错误.脚本中发生错误(通过数据验证或抛出异常),并且您希望脚本正常处理它。

方案 : 对于异常,使用脚本语言的异常处理机制(通常是try / catch块)。对于数据验证,您可以使用类似的方法,但定义一个布尔变量,如“valid”和if / else子句。ExecuteScript定义“成功”和“失败”关系; 通常,您的处理将“好”流文件转移到成功,“坏”流文件转换为失败(在后一种情况下记录错误).

Groovy

flowFile = session.get()
if(!flowFile) return
try {
  // Something that might throw an exception here
 
  // Last operation is transfer to success (failures handled in the catch block)
  session.transfer(flowFile, REL_SUCCESS)
} catch(e) {
  log.error('Something went wrong', e)
  session.transfer(flowFile, REL_FAILURE)
}

Jython

flowFile = session.get()
if(flowFile != None):
    try:
        # Something that might throw an exception here
       
        # Last operation is transfer to success (failures handled in the catch block)
        session.transfer(flowFile, REL_SUCCESS)
    except:
        log.error('Something went wrong', e)
        session.transfer(flowFile, REL_FAILURE)
# implicit return at the end

JavaScript

var flowFile = session.get();
if(flowFile != null) {
  try {
    // Something that might throw an exception here
 
    // Last operation is transfer to success (failures handled in the catch block)
    session.transfer(flowFile, REL_SUCCESS)
} catch(e) {
  log.error('Something went wrong', e)
  session.transfer(flowFile, REL_FAILURE)
}
}

JRuby

flowFile = session.get()
if flowFile != nil
  begin
    # Something that might raise an exception here
    
    # Last operation is transfer to success (failures handled in the rescue block)
    session.transfer(flowFile, REL_SUCCESS)
  rescue Exception => e 
    log.error('Something went wrong', e)
    session.transfer(flowFile, REL_FAILURE)
  end
end

希望本文描述了FlowFile I / O的基础知识和错误处理,但欢迎提出建议和改进!在本系列的下一篇文章中,我将讨论一些更高级的功能,例如动态属性,模块,状态管理以及访问/使用Controller Services.

 

原文连接:https://community.hortonworks.com/articles/75545/executescript-cookbook-part-2.html

你可能感兴趣的:(Nifi)