From 5622974582da94ec5da4b294e306de68168818dc Mon Sep 17 00:00:00 2001 From: Jeremy Dormitzer Date: Wed, 12 Jun 2019 22:29:12 -0400 Subject: [PATCH] Fix syntax; add last blog post; more styling --- .../activitypub-good-enough-for-jazz.html.pm | 6 +- src/blog/annnouncing-pterotype.html.pm | 12 +- src/blog/index.html.pm | 2 + src/blog/more-than-json.html.pm | 111 ++++++++++++++++++ src/pollen.rkt | 5 + src/stylesheet.css.pp | 19 ++- src/template.html.p | 7 +- 7 files changed, 151 insertions(+), 11 deletions(-) create mode 100644 src/blog/more-than-json.html.pm diff --git a/src/blog/activitypub-good-enough-for-jazz.html.pm b/src/blog/activitypub-good-enough-for-jazz.html.pm index 34e84f5..54c91c0 100644 --- a/src/blog/activitypub-good-enough-for-jazz.html.pm +++ b/src/blog/activitypub-good-enough-for-jazz.html.pm @@ -5,20 +5,20 @@ ◊heading-image[#:src "/images/activitypub.png"] -@link[#:href "https://pleroma.site/users/kaniini"]{Kaniini}, one of the lead developers of Pleroma, recently published a blog post called @link[#:href "https://blog.dereferenced.org/activitypub-the-worse-is-better-approach-to-federated-social-networking"]{ActivityPub: The “Worse is Better” Approach to Federated Social Networking}. It’s a critique of the security and safety of the @link[#:href "https://jeremydormitzer.com/blog/what-is-activitypub-and-how-will-it-change-the-internet/"]{ActivityPub protocol}. They make some good points: +◊link[#:href "https://pleroma.site/users/kaniini"]{Kaniini}, one of the lead developers of Pleroma, recently published a blog post called ◊link[#:href "https://blog.dereferenced.org/activitypub-the-worse-is-better-approach-to-federated-social-networking"]{ActivityPub: The “Worse is Better” Approach to Federated Social Networking}. It’s a critique of the security and safety of the ◊link[#:href "https://jeremydormitzer.com/blog/what-is-activitypub-and-how-will-it-change-the-internet/"]{ActivityPub protocol}. They make some good points: ◊ul{ ◊li{ActivityPub doesn’t support fine-grained access control checks, e.g. I want someone to be able to see my posts but not respond to them} ◊li{Instances you’ve banned can still see threads from your instance in some ActivityPub implementations, because someone from a third instance replies to the thread and that reply reaches the banned instance} } -The post also generated an @link[#:href "https://playvicious.social/@Are0h/101372851868909058"]{interesting Fediverse thread} discussing the tradeoffs between proliferating the existing protocol versus making changes to it, and whether it would be possible to improve the protocol without breaking backward compatibility. It’s worth a read. +The post also generated an ◊link[#:href "https://playvicious.social/@Are0h/101372851868909058"]{interesting Fediverse thread} discussing the tradeoffs between proliferating the existing protocol versus making changes to it, and whether it would be possible to improve the protocol without breaking backward compatibility. It’s worth a read. Here’s the thing: ActivityPub is a protocol, and protocols are only valuable as long as there is software out there actually using the protocol. At the end of the day, that’s the most important measure of success. Don’t get me wrong – protocols need to do the job they set out to do well. But at some point, the protocol works well enough that it becomes more important to foster adoption than to continue improving. I believe that ActivityPub has reached that point. Now, I’m not suggesting that we stop development on the protocol. But future improvements to it should be iterative, building on the existing specification, and backward compatible whenever possible. For example, by all means let’s come up with a better access control model for ActivityPub – but we should also come up with a compatibility layer that assumes some default set of access capabilities for implementations that haven’t upgraded. This lets us move forward without leaving the protocol’s participants behind, preserving ActivityPub’s value. -We are in good company here. This model is exactly how HTTP became the protocol that powers the internet. If you have the time, check out this @link[#:href "https://hpbn.co/brief-history-of-http/"]{excellent (brief) history} of the HTTP protocol. Here are the highlights: Tim Berners-Lee came up with HTTP 0.9, which was an extremely simple protocol that allowed clients to request a resource and receive a response. HTTP 1.0 added headers and a variety of other features. HTTP 1.1 added performance optimizations and fixed ambiguities in the 1.0 specification. +We are in good company here. This model is exactly how HTTP became the protocol that powers the internet. If you have the time, check out this ◊link[#:href "https://hpbn.co/brief-history-of-http/"]{excellent (brief) history} of the HTTP protocol. Here are the highlights: Tim Berners-Lee came up with HTTP 0.9, which was an extremely simple protocol that allowed clients to request a resource and receive a response. HTTP 1.0 added headers and a variety of other features. HTTP 1.1 added performance optimizations and fixed ambiguities in the 1.0 specification. Critically, all of these versions of HTTP were similar enough that a server that supported HTTP 1.1 could trivially also support HTTP 1.0 and 0.9 (because 0.9 is actually a subset of 1.1). In fact, the Apache and Nginx web servers, which power most websites on the internet, still support HTTP 0.9! By designing and iterating on HTTP in a way that preserved backward compatibility, the early web pioneers were able to build a robust, performant, secure protocol while still encouraging global adoption. diff --git a/src/blog/annnouncing-pterotype.html.pm b/src/blog/annnouncing-pterotype.html.pm index 516cf07..5d742b5 100644 --- a/src/blog/annnouncing-pterotype.html.pm +++ b/src/blog/annnouncing-pterotype.html.pm @@ -5,13 +5,13 @@ ◊header-image[#:src "/images/pterotype.png"] -In @link[#:href "https://jeremydormitzer.com/blog/what-is-activitypub.html"]{my last post}, I wrote about an emerging web standard called ActivityPub that lets web services interoperate and form a federated, open social network. I made an argument about how important this new standard is – how it tears down walled gardens, discourages monopolies and centralization, and encourages user freedom. +In ◊link[#:href "https://jeremydormitzer.com/blog/what-is-activitypub.html"]{my last post}, I wrote about an emerging web standard called ActivityPub that lets web services interoperate and form a federated, open social network. I made an argument about how important this new standard is – how it tears down walled gardens, discourages monopolies and centralization, and encourages user freedom. -I genuinely believe what I wrote, too. And so, to put my money where my mouth is, I’m excited to announce @link[#:href "https://getpterotype.com/"]{Pterotype}! It’s a WordPress plugin that gives your blog an ActivityPub feed so that it can take advantage of all the benefits ActivityPub has to offer. +I genuinely believe what I wrote, too. And so, to put my money where my mouth is, I’m excited to announce ◊link[#:href "https://getpterotype.com/"]{Pterotype}! It’s a WordPress plugin that gives your blog an ActivityPub feed so that it can take advantage of all the benefits ActivityPub has to offer. ◊heading{Why WordPress?} -My mission is to open up the entire internet. I want every website, every social network, and every blog to be a part of the Fediverse. And WordPress @link[#:href "https://w3techs.com/technologies/overview/content_management/all"]{runs literally 30% of the internet}. It’s not my favorite piece of software, and I certainly never expected to write any PHP, but the fact is that writing a WordPress plugin is the highest-impact way to grow the Fediverse the fastest. +My mission is to open up the entire internet. I want every website, every social network, and every blog to be a part of the Fediverse. And WordPress ◊link[#:href "https://w3techs.com/technologies/overview/content_management/all"]{runs literally 30% of the internet}. It’s not my favorite piece of software, and I certainly never expected to write any PHP, but the fact is that writing a WordPress plugin is the highest-impact way to grow the Fediverse the fastest. ◊heading{So wait, what does this actually do?} @@ -25,11 +25,11 @@ The plugin also syncs up comments between WordPress and the Fediverse. Replies f ◊heading{Sounds amazing! Can I use it now?} -Yes, with caveats. Pterotype is in early beta. The core features are in there – your blog will get a Fediverse profile, posts will federate, and comments will sync up – but it’s a pretty fiddly (and sometimes buggy) experience at the moment. If you do want to try it out, the plugin is in the @link[#:href "https://wordpress.org/plugins/pterotype/"]{plugin repository}. If you install it on your blog, please consider @link[#:href "https://getpterotype.com/beta"]{signing up for the beta program} as well – it’s how I’m collecting feedback and bug reports so I can make the plugin the best that it can be. +Yes, with caveats. Pterotype is in early beta. The core features are in there – your blog will get a Fediverse profile, posts will federate, and comments will sync up – but it’s a pretty fiddly (and sometimes buggy) experience at the moment. If you do want to try it out, the plugin is in the ◊link[#:href "https://wordpress.org/plugins/pterotype/"]{plugin repository}. If you install it on your blog, please consider ◊link[#:href "https://getpterotype.com/beta"]{signing up for the beta program} as well – it’s how I’m collecting feedback and bug reports so I can make the plugin the best that it can be. -If you’d rather just follow my progress and dive in when it’s finished, that’s fine too! I made my development roadmap @link[#:href "https://getpterotype.com/roadmap"]{publicly available}, and the plugin itself is open-source @link[#:href "https://github.com/pterotype-project/pterotype"]{on GitHub}. I’m currently doing a major refactor, pulling out all of the ActivityPub-related logic @link[#:href "https://github.com/pterotype-project/activitypub-php"]{into its own library} – once that’s done, it’ll be back to business as usual adding features and stability to Pterotype. +If you’d rather just follow my progress and dive in when it’s finished, that’s fine too! I made my development roadmap ◊link[#:href "https://getpterotype.com/roadmap"]{publicly available}, and the plugin itself is open-source ◊link[#:href "https://github.com/pterotype-project/pterotype"]{on GitHub}. I’m currently doing a major refactor, pulling out all of the ActivityPub-related logic ◊link[#:href "https://github.com/pterotype-project/activitypub-php"]{into its own library} – once that’s done, it’ll be back to business as usual adding features and stability to Pterotype. -If you’ve read this far, and this project resonates with you, then you might be interested in @link[#:href "https://www.patreon.com/pterotype"]{becoming a sponsor on Patreon}. Pterotype is free and open-source, so this is its only source of funding. For moment-to-moment updates, you can @link[#:href "https://mastodon.technology/@jdormit"]{follow me on Mastodon}. +If you’ve read this far, and this project resonates with you, then you might be interested in ◊link[#:href "https://www.patreon.com/pterotype"]{becoming a sponsor on Patreon}. Pterotype is free and open-source, so this is its only source of funding. For moment-to-moment updates, you can ◊link[#:href "https://mastodon.technology/@jdormit"]{follow me on Mastodon}. See you on the Fediverse! diff --git a/src/blog/index.html.pm b/src/blog/index.html.pm index 4cd6fbf..f716485 100644 --- a/src/blog/index.html.pm +++ b/src/blog/index.html.pm @@ -28,3 +28,5 @@ ◊(let ((rendered-posts (add-between (map render-post (get-posts)) (divider)))) `(div ,@rendered-posts)) + +◊(define-meta browser-title "Jeremy Dormitzer's blog") \ No newline at end of file diff --git a/src/blog/more-than-json.html.pm b/src/blog/more-than-json.html.pm new file mode 100644 index 0000000..34e9f0e --- /dev/null +++ b/src/blog/more-than-json.html.pm @@ -0,0 +1,111 @@ +#lang pollen + +◊(define-meta title "More than JSON: ActivityPub and JSON-LD") +◊(define-meta published "2019-04-23") + +◊blockquote{◊italic{In which our hero discovers the power of normalization and JSON-LD}} + +◊heading{The problem with JSON} + +I’ve been doing a lot of research for my current side project, ◊link[#:href "https://jeremydormitzer.com/blog/announcing-pterotype/"]{Pterotype}. It’s a new kind of social network built as a WordPress plugin that respects your freedom, encourages choice, and interoperates with existing social networks through the power of ◊link[#:href "https://jeremydormitzer.com/blog/what-is-activitypub-and-how-will-it-change-the-internet/"]{ActivityPub}. It’s undergone several iterations already – the beta has been out for a while now, and I’ve been working hard on a version 2 for the last several months. + +One of the things I wasn’t satisfied with in the first version of Pterotype was the way it stores incoming data. ActivityPub messages are serialized in a dialect of JSON called ◊link[#:href "https://json-ld.org/"]{JSON-LD}. I didn’t really get JSON-LD when I started this project. It seems overcomplicated and confusing, and I was more interested in shipping something that worked than understanding the theoretical underpinnings of the federated web. So I just kept the incoming data in JSON format. This worked, sort of, but I kept running into annoying, hard-to-reason about situations. For example, consider this ActivityPub object, representing a new note that Sally published: + +◊codeblock[#:lang "json"]{ + { + "@context": "https://www.w3.org/ns/activitystreams", + "id": "https://example.org/activities/1", + "type": "Create", + "actor": { + "type": "Person", + "id": "https://example.org/sally", + "name": "Sally" + }, + "object": { + "id": "https://example.org/notes/1", + "type": "Note", + "content": "This is a simple note" + }, + "published": "2015-01-25T12:34:56Z" + } + } + +The problem is that the above object, according to the ActivityPub specification, is semantically equivalent to this one: + +◊codeblock[#:lang "json"]{ +{ + "@context": "https://www.w3.org/ns/activitystreams", + "id": "https://example.org/activities/1", + "type": "Create", + "actor": "https://example.org/sally", + "object": "https://example.org/notes/1", + "published": "2015-01-25T12:34:56Z" + } + } + +This is the object graph in action – the ◊code{actor} and ◊code{object} properties are pointers to other objects, and as such they can either be JSON objects embedded within the ◊code{Create} activity, or URIs that dereference to the actual object (dereferencing is a fancy word for following the URI and replacing it with whatever JSON object is on the other side). Since I was representing these ActivityPub objects in this JSON format, that meant that whenever I saw an ◊code{actor} or ◊code{object} property, I always had to check whether it was an object or a URI and if it was a URI I had to dereference it to the proper object. This led to tons of annoying boilerplate and conditionals: + +◊codeblock[#:lang "php"]{ +if ( is_string( $activity['object'] ) ) { + $activity['object'] = dereference_object( $activity['object'] ); +} +} + +Yikes. So I came up with what I thought was a clever solution: just walk the object graph and dereference every URI I found whenever I saw a new JSON object. So I would receive Sally’s ◊code{Create} activity and traverse the JSON representation of its graph, dereferencing the ◊code{actor} and ◊code{object} objects in the process. This effectively turned the second representation above into the first one. Problem solved, right? + +Well, not quite. There are actually a bunch of problems with that approach. First, not all URIs in the JSON object should be dereferenced. For example, there is an ActivityPub attribute called ◊code{url} that is – you guessed it – a URL! And it is supposed to stay a URL, not get dereferenced to some other thing. Okay, so I’ll only dereference URIs that belong to attributes I know should contain references to other objects – ◊code{actor}, ◊code{object}, etc. But there’s still a problem! There’s no guarantee that we’ll be able to successfully dereference a URI. Maybe the server that was hosting that object went down. Maybe there’s a temporary network failure. Maybe it’s the year 3000 and bitrot has taken down 80% of the internet. The point is, even if we preemptively dereference all the URIs we can, we still need to handle the case where we couldn’t access the actual object and are stuck with the URI. Which means we still need those stupid conditionals everywhere! + +◊heading{JSON-LD to the rescue} + +So what’s the actual solution for this? Well, as it turns out these were exactly the types of issues that JSON-LD is designed to solve. JSON-LD provides a way to normalize data into a standard form based on a ◊italic{context} that defines a schema for the data. Here’s the second version of Sally’s activity from above after undergoing JSON-LD expansion: + +◊codeblock[#:lang "json"]{ +[ + { + "https://www.w3.org/ns/activitystreams#actor": [ + { + "@id": "https://example.org/sally" + } + ], + "@id": "https://example.org/activities/1", + "https://www.w3.org/ns/activitystreams#object": [ + { + "@id": "https://example.org/notes/1" + } + ], + "https://www.w3.org/ns/activitystreams#published": [ + { + "@type": "http://www.w3.org/2001/XMLSchema#dateTime", + "@value": "2015-01-25T12:34:56Z" + } + ], + "@type": [ + "https://www.w3.org/ns/activitystreams#Create" + ] + } + ] + } + +So what’s up with those weird URL-looking attributes? And why has everything become an array? + +The expansion algorithm has normalized the data into a form that is supposed to be universally normalized. The attributes – ◊code{object}, ◊code{actor}, etc. – have become URIs with a universal meaning and a known schema. In other words, any application that speaks JSON-LD knows what an ◊code{https://www.w3.org/ns/activitystreams#actor} is, even if they don’t know what an actor is. + +Importantly for our purposes, take a look at what the ◊code{object} field has turned into. We went from: + +◊codeblock[#:lang "json"]{ +"object": "https://example.org/notes/1" +} + +To: + +◊codeblock[#:lang "json"]{ +"https://www.w3.org/ns/activitystreams#object": [ + { + "@id": "https://example.org/notes/1" + } +] +} + +Because the object attribute is specified in the ◊link[#:href "https://www.w3.org/ns/activitystreams.jsonld"]{ActivityStreams JSON-LD vocabulary} to be of ◊code{@type}: ◊code{@id}, the expansion process was able to infer that ◊code{object} ought to be, well, an object. This neatly solves the problem of “is this string attribute actually a reference” – all references are clearly marked by their ◊code{@id} attributes now. Plus, this allows us to be smarter about when we dereference an object – for example, we can defer dereferencing until we actually need to access the attributes of the linked object. This approach also addresses the problem of network errors when dereferencing – if we can’t dereference, we just end up with an object that has only an ◊code{@id}, which can still be handled gracefully by the application. + +Hopefully this gave some insight into the types of challenges involved with building ActivityPub-powered applications and the point of JSON-LD. Have questions? Did I do something wrong? Let me know in the comments or on the ◊link[#:href "https://mastodon.technology/@jdormit"]{Fediverse}! \ No newline at end of file diff --git a/src/pollen.rkt b/src/pollen.rkt index f3ac0f6..2840b1c 100644 --- a/src/pollen.rkt +++ b/src/pollen.rkt @@ -89,3 +89,8 @@ (define (divider) (txexpr 'div '((class "divider")))) + +(define italic + (make-keyword-procedure + (lambda (kws kw-args . elements) + (txexpr 'em (zip-kws kws kw-args) elements)))) diff --git a/src/stylesheet.css.pp b/src/stylesheet.css.pp index abf54ba..efef4cd 100644 --- a/src/stylesheet.css.pp +++ b/src/stylesheet.css.pp @@ -10,6 +10,8 @@ ◊(define link-visited-color "purple") ◊(define nav-hover-color "#707070") ◊(define divider-color "#CDCDCD") +◊(define blockquote-border-color "#CCC") +◊(define blockquote-background-color "#F9F9F9") body { height: 100%; @@ -60,6 +62,21 @@ code, pre { font-size: 20px; } +ul, ol { + margin: 0 0 1.5em 3em; +} + +li { + margin-top: 1em; +} + +blockquote { + margin: 1em 1.5em; + border-left: 10px solid ◊|blockquote-border-color|; + background: ◊|blockquote-background-color|; + padding: 1.3em 0.5em; +} + pre > code.hljs { padding: 1.5em; } @@ -81,7 +98,7 @@ pre > code.hljs { .content { margin-top: ◊|navbar-height|px; - grid-column: 4 / 10; + grid-column: 5 / 10; hyphens: auto; } diff --git a/src/template.html.p b/src/template.html.p index a8a5d63..5e856c3 100644 --- a/src/template.html.p +++ b/src/template.html.p @@ -1,7 +1,12 @@ +◊(define the-title + (or (select-from-metas 'browser-title (current-metas)) + (select 'h1 doc) + "Jeremy Dormitzer")) + - ◊(or (select 'h1 doc) "Jeremy Dormitzer") + ◊|the-title|