Fork me on GitHub
#beginners
<
2023-12-27
>
Vincent02:12:04

Working on clientside browser cljs playback of click events. Every time someone "votes' on a thing, I have a musical note surf up the page. It is easy to spawn them because I add them to an atom and watch the atom for changes, but when someone loads the page it shows them all at once marching across like a gigantic company front. I would like to have them "play back" in a sequential and not-simultaneous fashion when people visit the page a'fresh.

Omer ZAK07:12:25

(This is not a question, but a report on the via dolorosa I am undergoing while trying to start using Clojure - with the hope that documentation and maybe software will be improve to make it easier for later newbies to start using Clojure.) While I am an experienced Linux user, I am newbie to Clojure and so I ran into difficulties when trying to use it. I'm reporting here about them. I use Linux Debian 11 (Debian Bullseye, currently oldstable). I wanted to install the Debian way and installed the 'clojure' package (which is version 1.10.2-1 in Debian Bullseye). The 'clojure -r' command worked for me. I prefer to use Emacs and so installed the Clojure/CIDER related packages. Then I found that when I try to start a Clojure REPL from cider-mode, I get the error message: "Execution error (FileNotFoundException) at http://java.io.FileInputStream/open0 (FileInputStream.java:-2).\n-Sdeps (No such file or directory)\n" This slack has some other people report similar bugs but I saw no relevant suggestions to resolve the bug. I looked in http://bugs.debian.org for bugs in the Clojure package, and it turns out that this package is not well maintained. The recommended way to install is via the non-apt based Linux script installer.

vemv10:12:57

-Sdeps (No such file or directory) could indicate a quote escaping issue. I'd make sure that: • The clojure CLI tool is the latest (not from debian) • CIDER (the Emacs package) is the latest stable release (https://github.com/clojure-emacs/cider/releases) If it persists, feel free to create an issue in the cider tracker

vemv10:12:02

Unrelated, but we have a debian-specific step that can avoid a frequent pitfall: https://docs.cider.mx/cider/troubleshooting.html#navigation-to-jdk-sources-doesnt-work

Omer ZAK07:12:33

I installed Clojure in my Debian Bullseye (Debian 11, oldstable) according to the recommended way of using Linux script. When trying to run 'clj' and 'clojure', I got the error message: "Error building classpath. Failed to read artifact descriptor for org.clojure:clojure:1.11.1" (had to add underscores to turn off Slack's iconization). According to error reports from other people, it seems that they experience this error when their machines have a network connection problem, which does not seem to be my case. What do I need to check? UPDATE: In my case, the problem turned out to be a missing certificate for http://repo1.maven.org. 1. Using your browser, browse to https://repo1.maven.org 2. Click on the Lock button in the URL box and view the domain's certificate. 3. Look for the "export" button and save the exported certificate in a file, let's call it repo1-maven.cacert 4. Import the certificate into the keystore used by Java. It is the file $JAVA_HOME/lib/security/cacerts (assuming that your $JAVA_HOME is set correctly). In Debian, this file is a symbolic link to /etc/ssl/certs/java/cacerts. The command for importing is: sudo keytool -keystore $JAVA_HOME/lib/security/cacerts -import -file repo1-maven.cacert

👀 1
Alex Miller (Clojure team)07:12:25

It should be trying to retrieve this file https://repo1.maven.org/maven2/org/clojure/clojure/1.11.1/clojure-1.11.1.pom - can you access it? If so, can you write to ~/.m2/repository? Might also be good to check java -version

Alex Miller (Clojure team)07:12:26

(should be at least Java 1.8)

Omer ZAK08:12:32

$ ls -al ~/.m2/repository/org/clojure/clojure/1.11.1/ total 8 drwxr-xr-x 2 omer omer 4096 Dec 27 10:10 . drwxr-xr-x 3 omer omer 4096 Dec 27 09:09 .. $ java --version openjdk 17.0.9 2023-10-17 OpenJDK Runtime Environment (build 17.0.9+9-Debian-1deb11u1) OpenJDK 64-Bit Server VM (build 17.0.9+9-Debian-1deb11u1, mixed mode, sharing)

Omer ZAK08:12:23

Seems that I do not have <https:clojure-1.11.1.pom|clojure-1.11.1.pom> How to retrieve it?

Omer ZAK08:12:06

I manually retrieved the above .pom file and placed it in ~/.m2/repository/org/clojure/clojure/1.11.1/ Now clj fails to read org.clojure:spec.alpha:jar:0.3.218 (I added underscores). Seems that I have a problem getting all *.pom files that clj needs. What to check now?

practicalli-johnny13:12:52

Downloading individual dependencies will add many hours (week) of work to use Clojure (and most other programming languages My assumption is that some certificates are outdated in the older version of Debian being used. The current stable version is Debian 12 (bookworm). Suggest updating to Debian 12 to see if these network challenges are resolved Bookworm is working correctly for me (and also by others in the community). Or it could be a network, firewall or proxy issue, but seems like an OS issue rather than Clojure or Java install.

Alex Miller (Clojure team)14:12:12

Agreed, I think something like that is a good guess, although the carts should be part of the Java install and this seems new enough

practicalli-johnny15:12:57

There is a separate ca-certificates-java package which is installed as a dependency of openjdk packages. It could be the certs in were not updated sufficiently as bullseye is no longer the current stable version. It could be a matter of reinstalling the ca-certificates-java (or dpkg-reconfigure) Otherwise it may be simpler in the long run to update the Debian distribution to the current stable version and benefit from all the recent security updates

phill22:12:21

Furthermore, in case of a problem retrieving something, Maven annoyingly saves a placeholder reminding it "this doesn't work" and it won't really try again until you do something extraordinary. This can delay your discovery that you have fixed the root-cause problem. (The placeholders are files in your ~/.m2/repository directory tree.)

Alex Miller (Clojure team)22:12:08

by default it will re-try once a day. deps.edn does support settings to override that to always if needed

Omer ZAK22:12:17

What would qualify as "something extraordinary" in this context? Deletion of the entire ~/.m2/repository tree?

Alex Miller (Clojure team)22:12:40

you can do a more targeted deletion of course - ~/.m2/repository/org/clojure/clojure/1.11.1 for example

Nim Sadeh17:12:51

Am I write in saying "In Clojure, when a thread throws an exception, the thread that calls it will not crash, even without handling the exception"? That's what I gathered from this experiment

(comment (let [f (fn [] (throw (ex-info "Fake exception" {})))]
           (thread-call f)
           (+ 1 2 3)) => 6)

hiredman18:12:28

There is no such thing as calling a thread

hiredman18:12:01

thread-call means "call this function on another thread"

hiredman18:12:54

Exceptions as phenomena are local to a thread, they unwind the stack on the thread where they are thrown

Nim Sadeh18:12:47

What happens to the thread that threw through exception? Is it deleted?

Nim Sadeh18:12:28

Also, I used the https://github.com/reborg/parallel?tab=readme-ov-file#ppmap library to run through a list of operations with 100 concurrent threads. When an operation threw an exception, the main thread also threw. That's the origin of why I was confused about this

hiredman18:12:12

thread-call doesn't get used much, future is used much more often, so I don't recall the implementation of thread-call, but I believe it actually uses a thread pool under the covers, so the thread may hang around for a bit waiting to see if there is any more work to pick up before getting stopped

hiredman18:12:40

That is a choice the library writer made to propagate exceptions like that

hiredman18:12:55

If you are trying to use real threads with core.async, use clojure.core.async/thread

hiredman18:12:51

It is like go, but uses a real thread so it is fine for blocking operations (and you have to use the blocking channel ops)

👍 1
Nim Sadeh18:12:29

I must be doing something wrong but I have spent an hour on this and I am out of ideas: I have a service calling an API endpoint that started throwing 403/forbidden errors. I assumed it hit some kind of rate limit so I paused it for 10, then 20 minutes. I also swapped out tokens, but nothing helped. Before reaching out to the API owners to see if everything is OK, I ran through the Jupyter Lab that they give out as documentation to make sure it's also broken there, as that's what they'd have me do to verify. Using the same token, same query, it works perfectly well in Jupyter Lab/python 3.11 with the requests library. I hard-coded all the parameters (queries, headers, address, etc.) and copied over to the Clojure repl. Using clj-http, I get 403. Using Python, I get 200. I repeated this several times and made sure I hard-copied all the right parameters and that both requests use the exact same parameters. Not sure how this could be possible.

Omer ZAK19:12:43

How sure are you that the query messages are indeed equal (encodings, new line handline, escaping special characters, etc.)? You may want to sniff the communications using Wireshark and compare the outputs of your scripts.

Nim Sadeh19:12:03

100% sure, I simplified it to just a couple of strings so there are no lines, no special characters, etc. It's also a brand new thing, script has been running on a droplet for about 8 hours now without issues and suddenly 403s. Local REPL running queries that previously worked also 403s.

Omer ZAK19:12:36

Some wild guesses: 1. Software upgrades done in the background degraded certificates. 2. A certificate has just expired and needs to be renewed.

Nim Sadeh19:12:51

Haven't been able to see anything useful with Wireshark but I am not exactly a power user

Nim Sadeh19:12:58

Here's the two requests:

(client/get url {:query-params {"p" "001:1"
                                                     "format" "files"
                                                     }
                                      :headers {"Authorization" auth}
                                      }
                                 )
r = requests.get(url,
                params= {"p": "001:1", "format": "files"},
                headers={"Authorization": auth})
where url and auth are the same string

Nim Sadeh19:12:30

Is it possible for there to be some kind of fingerprinting based on the HTTP client?

hiredman19:12:28

The python code doesn't have valid quoting of strings, so hard to tell if it matches

Alex19:12:16

Probably worth dumping the raw requests ala curl -v in both cases and compare closely. A common mistake is to forget "Bearer " before the token auth, but that's usually responded with 401, not 403.

hiredman19:12:44

Given that the python code isn't valid, it seems like you aren't just copying and pasting the code you are actually running, which means you may be correcting whatever the error is when transcribing the code to share here

hiredman19:12:11

I would check that map keys are all at the right level

hiredman19:12:16

If you have been running full tilt for 8 hours it is entirely possible they decided your traffic from that ip was abusive

Nim Sadeh19:12:38

I checked with them for limits before I ran it, so it should be good. However, the clojure code also failed on my local machine. I ran the curl on both machines and got 200. Yea, this Python wasn't copy-pasted as I had to omit the tokens for slack, but it was in my testing

Nim Sadeh19:12:53

@U04CRS4B49L is there a way to get the raw request from clj-http?

hiredman19:12:50

The python code just isn't syntactically valid, so it is hard to tell if the query parameter names are the same

hiredman19:12:13

Ah, got fixed

👍 1
hiredman19:12:47

Does the 403 actually say anything?

Nim Sadeh20:12:34

It's an HTML page saying I don't have access:

"\n<html><head>\n<title>403 Forbidden</title>\n</head><body>\n<h1>Forbidden</h1>\n<p>You don't have permission to access this resource.</p>\n</body></html>\n",

hiredman20:12:25

And what do you get if you make the http request from python without the auth token?

Nim Sadeh20:12:06

Soemthing different:

b'{"error": "User guest is not authorized to perform runapi with parameters operation=read,endpoint=search"}'

Nim Sadeh20:12:26

I have my local operation spit out the auth token when it's making the request just to make sure

hiredman20:12:56

And what do you get on the clojure side without the auth token?

Alex20:12:29

Good question — not sure what's the easiest way to inspect the request in clj-http top of my head.

hiredman20:12:48

It will just return something the repl will print or throw an exception which the repl will print if you type in *e

hiredman20:12:35

clj-http throws exceptions on not successful http codes by default but there is an option you can pass in to not do that

Nim Sadeh20:12:41

The same HTML page as before, meaning that the API probably didn't do an auth check against my token at all

hiredman20:12:39

It sure sounds like the :headers key isn't being set on the right place

1
Nim Sadeh20:12:30

That wouldn't explain why a jar that's been working for hours suddenly started throwing that error though

hiredman20:12:04

The fact that the clojure side and python side get different responses when not authed

hiredman20:12:10

Is very suspicious

Nim Sadeh20:12:55

I get the same HTML response when I put in this:

(client/get url {:query-params {"p" "001:1"
                                                     "format" "files"
                                                     }
                                      :headers {"Authorization" "Token abc"}
                                      })

Nim Sadeh20:12:11

(Token instead of Bearer is right, that's what the Python has as well)

hiredman20:12:41

Do you have a .netrc file?

Nim Sadeh20:12:00

I don't see one in my home directory

Alex20:12:04

Probably the easiest way is not in clj-http itself, but you can just capture the request by swapping the URL for a dummy HTTP server running locally, that will just print it. SimpleHttpServer in Python, for example

Alex20:12:46

(or even netcat)

hiredman20:12:20

Does the http server you are making requests give off the vibe of being home grown? I am extremely hesitant to suggest it, but maybe try a lower case A for the authorization header 😔

Nim Sadeh20:12:46

it's quite home-grown, it's run by the UN

Nim Sadeh20:12:03

(That wasn't sarcastic at you but at the UN)

Nim Sadeh20:12:43

127.0.0.1 - - [27/Dec/2023 15:34:09] "GET / HTTP/1.1" 200 -
ERROR:root:Host: 127.0.0.1:8000
User-Agent: curl/8.1.2
Accept: */*
Authorization: Token <my-token>


127.0.0.1 - - [27/Dec/2023 15:34:35] "GET /?p=001:1&format=files HTTP/1.1" 200 -
ERROR:root:Connection: close
Authorization: Token <my-token>
accept-encoding: gzip, deflate
Host: 127.0.0.1:8000
User-Agent: Apache-HttpClient/4.5.13 (Java/21.0.1)


127.0.0.1 - - [27/Dec/2023 15:34:46] "GET /?p=001%3A1&format=files HTTP/1.1" 200 -
ERROR:root:Host: 127.0.0.1:8000
User-Agent: python-requests/2.31.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Authorization: Token <my-token>
This is the result of Alex's suggestion to use Simple HTTPServer (TIL) to print out the headers The only thing altered in the above was replacing the token with <my-token> In order, they originate from: 1. Curl 2. Clojure 3. Python Recall that 1 and 3 currently work on the actual prod server, and 2 doesn't

Nim Sadeh20:12:25

The code running the server has been lifted without change from https://gist.github.com/phrawzty/62540f146ee5e74ea1ab

hiredman20:12:20

Ah, you need to url encode the query parameters

Nim Sadeh20:12:38

I would expect that passing them in the correct slot :query-params would automatically encode them, no? Also, still unsure about how it very suddenly stopped working? Is it possible that a server update made that happen?

Nim Sadeh20:12:11

Because it can't be the client library, I got an uberjar that's been working for hours and then stopped working

Nim Sadeh20:12:53

Here's the code that calls the local simple HTTP server:

(client/get local-url {:query-params {"p" "001:1"
                                                     "format" "files"
                                                     }
                                      :headers {"Authorization" auth}
                                      })

hiredman20:12:18

If you are just going be the difference in the python request above and the clojure one that is it

hiredman20:12:51

: is not required to be percent encoded in query parameters but it can be

hiredman20:12:04

So the python code and the clojure code(likely just calling some java code) may just have differing interpretations of that

Nim Sadeh20:12:17

I encoded the query string to look like this in the simple server:

127.0.0.1 - - [27/Dec/2023 15:47:36] "GET /?p=001%3A1&format=files HTTP/1.1" 200 -
ERROR:root:Connection: close
Authorization: Token <my-token>
accept-encoding: gzip, deflate
Host: 127.0.0.1:8000
User-Agent: Apache-HttpClient/4.5.13 (Java/21.0.1)
Against the prod server, it gives the same 403 error with an HTML page

Nim Sadeh20:12:03

Wild theory, is it possible for someone to read "user agent" as an identifier of the user and to gate them from an API based on that header, maybe thinking it's a bot

Nim Sadeh20:12:38

Is there a universe where that makes sense, or is it always a mistake on the part of the person that would do that

Nim Sadeh20:12:46

And is it possible to set the user-agent manually

hiredman20:12:04

It is just another header

hiredman20:12:18

You can set it to whatever you want

Nim Sadeh20:12:05

Yea that was it

Nim Sadeh20:12:13

I set it to curl and sailed right past

picard-facepalm 1
hiredman20:12:51

If you feel malicious you can set to a random uuid every request

Nim Sadeh20:12:07

thanks both, I learned a lot. Is there a universe where a server deciding to block someone on this header makes sense? To me it appears nonsensical because of how easy it is to bypass?

Nim Sadeh20:12:17

I literally set it to Potato just now

Nim Sadeh20:12:14

I have a love/hate relationship with spending a long time debugging something purely under the illusion of there's no way the right solution is this dumb...

hiredman20:12:15

It is more annoying that they would block the default for the java apache client and not for the python lib

Alex20:12:17

It looks like a very naïve attempt at rate limiting, yes

Alex20:12:36

Probably set up by some high school student

hiredman20:12:57

They might have some automated system that blocks high traffic user agents

Nim Sadeh21:12:11

I literally asked them for a rate limit and they said they didn't think there was one, although they seem to think that a rate limit was a defect in the way they talked about it 🤷 I have a lot of work I need from their system but I was happy to do it on their terms, but if there are no terms (and the only thing I got from my initial testing was a 5 minute gateway timeout) I wanted to do it as fast as possible

hiredman21:12:31

It is very easy to bypass, but some people consider it good etiquette to set a custom user agent if you are going to be making lots of requests

👀 1
Nim Sadeh21:12:45

Does this mean all Apache clients on this version are blocked unless they manually set their user agents?

hiredman21:12:16

Crawlers for search engines typically set very specific user agents for example

1
Alex21:12:20

It may still be a combination of user agent and source subnet

hiredman21:12:26

Depends how they do the block

Nim Sadeh21:12:32

That's good to know, although I did notify them in advance that I will want to and asked if there's anything I should do

hiredman21:12:55

Yeah, could be blanket user agent, could be user agent + other info

hiredman21:12:44

Depending on the structure of the team the blocking behavior may even belong to someone else

hiredman21:12:54

If you are taking to the devs of code serving whatever, it could be an ops team running a proxy in the middle somewhere that is blocking traffic they think is suspicious

Nim Sadeh21:12:07

I get web crawlers because they're protecting a public resource, but this is auth-protected. Why not disable the token?

Nim Sadeh21:12:48

New jar is deployed and running in prod, thanks!

Greg Meltzer23:12:21

am I implementing a code smell, or is there a better way? I've got an app using clojure in the backend and clojurescript in the front. In the backend, my data is using fully qualified keywords like :pigeon-scoops.components.grocery-manager/type. This is fine in the backend where ::type or ::gm/type can be used, but in the front end, it seems like it needs to be typed out completely. Should I not be using fully qualified names on data that gets sent out of the backend or is there a better mechanism for using these keywords?

Fredrik23:12:11

You can use a shorter prefix such as grocery-manager, instead of the full namespace. So both in the back end and the front end you would be typing eg. :grocery-manager/type.

Fredrik23:12:01

But I have to ask, what is at about the front end that makes it impossible to use ::type or ::gm/type?

Greg Meltzer23:12:59

it could be a lack of knowledge. ::gm/type works in backend code where I'm requiring [pigeon-scoops.components.grocery-manager :as gm] at the top. I don't think I can import the namespace from clj in cljs?

Fredrik23:12:52

There is an :as-alias option to the require that should help you out 🙂 Example

(ns front-end-ns
  (:require [pigeon-scoops.components.grocery-manager :as-alias gm]))

::gm/type ;; => :pigeon-scoops.components.grocery-manager/type

Greg Meltzer23:12:50

Learn something new every day

hiredman00:12:10

I think using keyword namespaces that are the same as code namespaces is not very good

🎯 1
1
hiredman00:12:15

Keywords become part of the definition of data that is passed between programs and can be stored externally out living the process that creates it

Greg Meltzer00:12:46

hmm that's a good point

practicalli-johnny07:12:32

Defining specs in a .cljc (Clojure common) file would seem a useful approach when using Clojure and ClojureScript in the same project.