Automation Professionals' Blob Server Module

Works in my lab VMs with 8.3.2. Are you sure you installed the v8.3 module file on the v8.3 server? (Module files for v8.1 will not work on v8.3--not for anyone's module.)

If you did use the right file, please share the wrapper.log content that corresponds to the fault.

com.inductiveautomation.ignition.common.modules.ModuleLoadException: Unable to load hook class "com.automation_pros.blobserve.GatewayHook" for module "com.automation_pros.blobserve".
	at com.inductiveautomation.ignition.gateway.modules.ModuleInstance.loadHook(ModuleInstance.java:632)
	at com.inductiveautomation.ignition.gateway.modules.ModuleInstance.load(ModuleInstance.java:306)
	at com.inductiveautomation.ignition.gateway.modules.ModuleManagerImpl.loadModules(ModuleManagerImpl.java:306)
	at com.inductiveautomation.ignition.gateway.modules.ModuleManagerImpl.init(ModuleManagerImpl.java:162)
	at com.inductiveautomation.ignition.gateway.IgnitionGateway.startupInternal(IgnitionGateway.java:1120)
	at com.inductiveautomation.ignition.gateway.redundancy.RedundancyManagerImpl.startup(RedundancyManagerImpl.java:352)
	at com.inductiveautomation.ignition.gateway.IgnitionGateway.initRedundancy(IgnitionGateway.java:814)
	at com.inductiveautomation.ignition.gateway.IgnitionGateway.lambda$initInternal$1(IgnitionGateway.java:736)
	at com.inductiveautomation.ignition.common.execution.impl.BasicExecutionEngine$ThrowableCatchingRunnable.run(BasicExecutionEngine.java:550)
	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
	at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
	at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
	at java.base/java.lang.Thread.run(Unknown Source)
Caused by: java.lang.ClassNotFoundException: com.automation_pros.blobserve.GatewayHook
	at java.base/java.net.URLClassLoader.findClass(Unknown Source)
	at com.inductiveautomation.ignition.gateway.modules.ModuleClassLoader.findClass(ModuleClassLoader.java:37)
	at com.inductiveautomation.ignition.gateway.modules.ModuleClassLoader.loadClass(ModuleClassLoader.java:104)
	at java.base/java.lang.ClassLoader.loadClass(Unknown Source)
	at com.inductiveautomation.ignition.gateway.modules.ModuleInstance.loadHook(ModuleInstance.java:609)
	at com.inductiveautomation.ignition.gateway.modules.ModuleInstance.load(ModuleInstance.java:306)
	at com.inductiveautomation.ignition.gateway.modules.ModuleManagerImpl.loadModules(ModuleManagerImpl.java:306)
	at com.inductiveautomation.ignition.gateway.modules.ModuleManagerImpl.init(ModuleManagerImpl.java:162)
	at com.inductiveautomation.ignition.gateway.IgnitionGateway.startupInternal(IgnitionGateway.java:1120)
	at com.inductiveautomation.ignition.gateway.redundancy.RedundancyManagerImpl.startup(RedundancyManagerImpl.java:352)
	at com.inductiveautomation.ignition.gateway.IgnitionGateway.initRedundancy(IgnitionGateway.java:814)
	at com.inductiveautomation.ignition.gateway.IgnitionGateway.lambda$initInternal$1(IgnitionGateway.java:736)
	at com.inductiveautomation.ignition.common.execution.impl.BasicExecutionEngine$ThrowableCatchingRunnable.run(BasicExecutionEngine.java:550)
	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
	at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
	at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
	at java.base/java.lang.Thread.run(Unknown Source)

That means the entire module failed to load. Is there no other relevant message immediately above that?

Does your module file end with ...-v83.modl?

Also, did your upgrade process insert some --add-opens=... additional java options? My v8.3 test VMs have these:

wrapper.java.additional.2=--add-opens=java.base/java.lang=ALL-UNNAMED
wrapper.java.additional.3=--add-opens=java.base/java.io=ALL-UNNAMED
wrapper.java.additional.4=--add-opens=java.base/java.security.cert=ALL-UNNAMED
wrapper.java.additional.5=--add-opens=java.base/java.util=ALL-UNNAMED
wrapper.java.additional.6=--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED

Java Additional Parameters

#wrapper.java.additional.=1-XX:+UseConcMarkSweepGC
#wrapper.java.additional.2=-XX:+CMSClassUnloadingEnabled
wrapper.java.additional.3=-Ddata.dir=data
wrapper.java.additional.4=-Dorg.apache.catalina.loader.WebappClassLoader.ENABLE_CLEAR_REFERENCES=false
#wrapper.java.additional.5=-Xdebug
#wrapper.java.additional.6=-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000
wrapper.java.additional.5=-Dedition=
wrapper.java.additional.6=--add-opens=java.base/java.lang=ALL-UNNAMED
wrapper.java.additional.7=--add-opens=java.base/java.security.cert=ALL-UNNAMED
wrapper.java.additional.8=--add-opens=java.base/java.util=ALL-UNNAMED
wrapper.java.additional.9=--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED
wrapper.java.additional.10=--add-opens=java.base/java.io=ALL-UNNAMED

image

Does clicking on the red "Faulted" notice take you to that traceback?

Consider uninstalling my module, restarting the gateway, and re-installing.

(I'm stumped--that error occurs without running any of my module's code, so there doesn't appear to be anything I could fix.)

The red faulted does take me to that error. There isn’t anything above that error in the wrapper log that is relevant except that the gateway was restarted. I uninstalled and the restarted the gateway, reinstalled and restarted the gateway again and it still has the error. perhaps the download didn’t go well and i should download again.

1 Like

Try checking my module file's signature:

$ sha256sum "Blob Serve-1.1.0.252721936-v83.modl"
88af4eaffebc4fb7235f9f3d093325df8cbc8b2cb91c436557ce6070d819e03a  Blob Serve-1.1.0.252721936-v83.modl
1 Like

Phil,

Thanks for the help. IT security was blocking the installation without any popup or notification that it was doing that. I called support and was able to get it installed.

3 Likes

Hi Phil,

Appreciate the work on your module, great work. We are trying to build something similar in our Perspective project: we have files stored on our server that we can load as blob content, and want to display the file in the browser. We have created a WebDev endpoint to serve the blob, but we run into authentication issues there. We are using SSO in our project, yet when we try to reach the endpoint in the browser an authentication prompt is still made. This is not workable for us. We are wondering how you present the blob content in the browser without using the webdev module.
Say we have blob content like this: blob = system.file.readFileAsBytes(file_path). How do you then display this in the browser?

Use the bytes dictionary key in the dictionary you return from doGet().

WebDev doesn't enforce any permissions by default.

If this doesn't help, show your complete WebDev doGet() method. (Use the preformatted text style on this forum for code.)

Any 3rd party module (Java) can add routes or complete servlets to Ignition. My Blob Server adds a servlet.

1 Like

Hi Phil,

I’m running into an “issue” with the Blob server and videos. Issue in quotes because it’s working fine, but writing a warning to the Gateway log every time a video is accessed. This is just an annoyance at the log entries, and not impacting functionality.

If I use a text file everything works as expected.

The video is a ~6 MB .webm and it plays fine when accessing it through the Blob server in a browser.

This warning is showing up in the gateway log though:

Unable to handle blob request path /blob/…


	org.eclipse.jetty.io.EofException: null
	at org.eclipse.jetty.io.SocketChannelEndPoint.flush(SocketChannelEndPoint.java:116)
	at org.eclipse.jetty.io.WriteFlusher.flush(WriteFlusher.java:422)
	at org.eclipse.jetty.io.WriteFlusher.completeWrite(WriteFlusher.java:377)
	at org.eclipse.jetty.io.SelectableChannelEndPoint$2.run(SelectableChannelEndPoint.java:67)
	at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.runTask(AdaptiveExecutionStrategy.java:421)
	at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.consumeTask(AdaptiveExecutionStrategy.java:375)
	at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.tryProduce(AdaptiveExecutionStrategy.java:277)
	at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.run(AdaptiveExecutionStrategy.java:199)
	at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:411)
	at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:969)
	at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1194)
	at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1149)
	at java.base/java.lang.Thread.run(Unknown Source)
	Caused by: java.io.IOException: Broken pipe
	at java.base/sun.nio.ch.FileDispatcherImpl.writev0(Native Method)
	at java.base/sun.nio.ch.SocketDispatcher.writev(Unknown Source)
	at java.base/sun.nio.ch.IOUtil.write(Unknown Source)
	at java.base/sun.nio.ch.IOUtil.write(Unknown Source)
	at java.base/sun.nio.ch.SocketChannelImpl.write(Unknown Source)
	at java.base/java.nio.channels.SocketChannel.write(Unknown Source)
	at org.eclipse.jetty.io.SocketChannelEndPoint.flush(SocketChannelEndPoint.java:110)

I would think that an artifact of size limits in perspective. Nothing my module can do about that. Also, large blobs do not play nice with NQ caches. Ergo my recommendations for segmenting videos.

2 Likes

I'm having the same issue as @rpavlicek that I can't update the pictures using .refreshBinding().

I have also tried using a throw-away parameter dateExtract(now({session.custom.updateTime}), 'second') but the problem with this one is that once a minute has passed, it uses the cached images from the last get-request. I could probably change it into now({session.custom.updateTime}) but then I will just fill the cache with old pictures and you recommended to NOT do this.

I'm doing a "surveillance system" on a production line by taking pictures from IP-cameras using a Gateway Timer Event. Then I present the pictures from the SQL table in a picture component in Perspective which is passed into a Flex repeater to be able to pass the cameraID into the right component.

How would I go about your recommendation to insert rows into the SQL table instead of updating?

Right now my table has a cameraID column and a image_blob column. My url uses the cameraID as the only parameter. So even if I add a thrid column, named ID which is primary key, my url won't change.
Do I need to fetch the new ID and pass that into the url instead? Or will it work using the cameraID because the SQL-query is not the same?

Expression binding on image component:

image

Result of SQL Select query:

image

There's no good answer. You are going to abuse the browser's image cache one way other other. Instead of using just the second as the throwaway parameter, consider using the full timestamp milliseconds.

Or add a column for image ID and autoincrement it on insert. Run a timer that prunes old images to clean up the DB.