gmuth / ipp-client-kotlin

A client implementation of the ipp protocol written in kotlin
MIT License
75 stars 10 forks source link

Get-Job-Attributes Failed: 'client-error-bad-request' after successful print from printer #19

Closed zacksd closed 7 months ago

zacksd commented 7 months ago

Hi,

Running version 3.1 on an Android app written in Java.

Here is my print execution code: `new Thread(() -> { try { DocumentFormat documentFormat = new DocumentFormat("application/vnd.hp-PCL"); File pclFile = new File(filepath); String jobIdentifier = "Print job: " + externalId; IppJob job = printer.printJob( pclFile, documentFormat, jobName(jobIdentifier), ColorMode.Auto, Sides.OneSided );

            getActivity().runOnUiThread(this::stopBlockingUI);
            job.waitForTermination();

        } catch (Exception e) {
            if (e.getMessage().equals("Get-Job-Attributes failed: 'client-error-bad-request'")) {
                ...
            } else if (e.getMessage().equals("timeout")) {
                ...
            } else {
                ...
            }
    }).start();`

I am printing to an HP printer.

I receive a client bad request error de.gmuth.ipp.client.IppExchangeException: Get-Job-Attributes failed: 'client-error-bad-request' every time I print this way even though my document has successfully printed in its entirety from my physical printer.

Here is the log from logcat:

image

This exception is raised half way through printing the terminal page of the document (tested with multipage document in addition to single page and for a multipage document, this error only raises while the last page of the document is printing).

I will also note this is happening when I pass .pcl files and .pwg (w/ DocumentFormat("image/pwg-raster")) files that are converted to their respective formats from PDF.

It is unclear to me what is causing the client-error-bad-request error to be thrown only when printing the terminal page (which prints successfully each time).

Lastly, I removed the call to job.waitForTermination() and caught errors in my catch block and I did not get the client-error-bad-request error raised. What are the risks of not calling this method when triggering a print job? My testing suggests that is IppPrinter.printJob(...) is a synchronous method that takes a few seconds to run, which I believe is how I caught errors in my catch block when I removed job.waitForTermination(). Does this method being synchronous negate the need to call job.waitForTermination() since that's where this mysterious client-error-bad-request is being surfaced? My first choice is to learn how to pinpoint the root cause of the client-error-bad-request issue, and then depending on risks of removing job.waitForTermination() will go that route if other error cannot be pinpointed with new leanings.

Thanks!

gmuth commented 7 months ago

This library only uses synchronous calls (for portability reasons). So you found a good solution for your issue. It looks like submitting the print document works. The IPP specification requires to support the Get-Job-Attributes operation. However it does not define for how long a printer should remember a job. So my guess is the printer does not support job status attributes. The risk of not monitoring the job is that you can't get informed about print issues. You could also call isTerminated(true) to check the job state - however this probably will fail too.

It would help if you log the IppExchangeException using the the .log(your-logger) method which should log the details of the response as well. I've pushed a commit in master to enable better support for this issue by logging the response.

zacksd commented 7 months ago

Thank you for your response for pushing this commit to help log and debug.

I think your theory of the printer not supporting job status attributes holds weight. It is a modern HP OfficeJet printer but that doesn't really mean much.

Here is a screenshot of the same error from my logger with your new log output:

image

I set breakpoints in your code to inspect the request and response objects. Here is a screenshot of the response object, not sure if this will be helpful, I didn't find anything out of the ordinary with it:

image

Thanks for you help!

gmuth commented 7 months ago

So HP printers usually have good IPP support. I can not imagine your printer not supporting Get-Job-Attributes for a job just printed. Your logging shows "nginx" as server. I've never seen a printer claiming to run nginx as http server. Have you setup nginx as a reverse proxy to access the HP printer via http? If so please test a direct connection between your application and the printer using the printers IP.

zacksd commented 7 months ago

Thanks for your response and helpful feedback.

I have not setup nginx as a reverse proxy nor are there any calls in my app to do this. My printer is on the same network as the device I am testing my app and my application code initializes the printer successfully with the IPv4 address on the printer's screen. I suspect the nginx http server is an idiosyncrasy of my wireless network (multi tenant business network). I gave two of my colleagues a development build that triggers a toast message if the Get-Job-Attributes failed:client-error-bad-request`` error is thrown to test if my network was to blame for raising the client error. Both of my colleagues are using recent HP OfficeJet printers like myself and both are on private, single family residential networks. I had them run two test print jobs from the app and both times the 3 page document printed successfully and the toast message appeared while the third page was printing. This leads me to think the nginx http server is a red herring on my end or is surfaced without my knowledge due to a design and/or implementation flaw in my app.

Lastly, I tested the isTerminated(true) approach.

I wrote this for loop solely for testing: `IppJob job = printer.printJob( pclmFile, documentFormat, jobName(jobIdentifier), ColorMode.Auto, Sides.OneSided );

int x = 0; while(!job.isTerminated(true)) { x++; } showError(String.valueOf(x));`

and the Get-Job-Attributes failed:client-error-bad-request` error was raised. I also ran the loop with(!job.isTerminated(false))` and this resulted in an infinite loop of incrementing x.

I am planning on leaving my code as it originally was, "quietly" logging this failures (both to logger and remotely) since the document is consistently printing and leaving all other failures as "loud" failures that will alert the user something has gone wrong. Given the information above, do you have any other suggestions to resolving my error? Thanks for your continued support of this issue.

gmuth commented 7 months ago

Thanks for your nginx investigation.

is... methods usually should not change the state of an object. However this library is about dealing with IPP objects whose real state exists only in the printer - so with option updateAttributes=true the job state attributes are queried from the printer to get the latest values/states. Calling waitForTermination() makes no sense when it always fails. For now you should just not try to monitor the job in the application.

I don't think the printer should reject the request. So I could just classify the manufacturers firmware as not conforming to the IPP spec. However that's usually not my way of doing things because it doesn't help. If you're willing to spend time I'd like you to try a few more things. There are still options we can twiggle on and see what happens.

timing

Some printers are not keeping up very well with IPP requests while busy with other operations. Does ippPrinter.getJobs() work at all? You may have to use getJobs(WhichJobs.Completed) or getJobs(WhichJobs.All) if you don't want to submit a print job for these tests. You can iterate over the jobs and just log the job.toString() output.

Or you could query the printer a few seconds/minutes later, if you remember the job-id: ippPrinter.getJob(job_number) If that works you can test slower polling: waitForTermination(Duration.ofSeconds(5)) or IppJob.defaultDelay = Duration.ofSeconds(5) overwrites the default of 1s

no user name

We could remove the optional requesting-user-name attribute from the request by setting ippPrinter.ippConfig.userName = null Usually printers don't care about authentication/users - so it's unlikely to change something.

use job-uri instead of printer-uri and job-id

set IppJob.useJobUri = true before calling waitForTermination()

specify attribute or a group

We can query attribute groups as specified here. e.g. call ippJob.getAttributes("all") or "job-description"

(if you want to save paper you can call ippJob.cancel() and still test monitoring the job)

gmuth commented 7 months ago

Feel free to reopen the issue in case my suggestions from above didn't help.

mathieuedet commented 1 month ago

Hi, I have discovered your library and you did a great work, thank you ! It seems that HP is not the only manufacturer where we can see this problem. I've bought an Epson ET-2850 and I'm encountering the same problem. Printing is OK but unable to use waitForTermination because of that.

When I debug and look at jobs by calling getJobs(Completed), I see my completed jobs (getJobs(All) throws IppExchangeException). I've already try to increase duration of waitForTermination to 10 second but the problem is the same. I don't know if you have reproduced the problem from your side since the last message.

Feel free to ask if you need additional information

gmuth commented 1 month ago

I'd be happy to investigate again for your epson printer if you open another issue for your printer and provide the logs and potentially saved ipp messages. If I had access to the specific printer I could look for workarounds. Does monitoring the job with ipptool work after being printed? The reporter of this issue did not provide the full response from the printer. Usually responses with this status code include information about unsupported attributes or values.