Elizabeth (Plabayo BV): This is netstack.fm, your weekly podcast about networking, Rust and everything in between. You are listening to episode 30, recorded on March 6th, 2026. In this episode, we speak with Martin algestan creator of uReq a synchronous HTTP client for Rust. We explore why uReq was created, how its design evolved. and whether protocol logic really needs to be tied to networking IO. Along the way, we look at the HTTP internals and the architecture behind uReq Let's begin.
Glen (Plabayo BV): Welcome for another week of Netstack.FM and with me is Martin. He was also the guest in episode 16 where we talked a bit about WebRTC and also introduced for the very first time on this podcast Sans IO So welcome Martin.
Martin Algesten: Thank you. Thank you very much. Nice to be here again.
Glen (Plabayo BV): Yeah, and so last time we also ⁓ mentioned a bit and talked a bit about uReq, which is a fantastic HTTP library that you created. As far as I know, it's only a client HTTP library, right?
Martin Algesten: That's right. It's only an HTTP client. ⁓ Filling a niche that maybe no other HTTP client is filling right now. But we will circle back to that a bit later, I think.
Glen (Plabayo BV): Yeah, and of course I say only, but as every HTTP implementer knows, even just the client side is not just only and Daniel from Curl can also talk about that given you could also say just a client library, but the immense of depth you have to go into implementing these protocols, making them work together and certainly HTTP is not getting simpler, but indeed we will get into that. So I think most people might still remember you, but still maybe you could give a short introduction of yourself, but you don't have to go as deep as last time.
Martin Algesten: Yeah, so basically I am a Swede living in Barcelona. I emigrated because I can't stand Swedish winters. And ⁓ I grew up in Sweden and sort of same height as Stockholm, but closer to Norway than to Stockholm. And then went to university up in North Sweden at Luleå, That was like, I remember the, I think the first ever RFC I read like cover to cover. I mean, RFC doesn't have cover, but the whole thing was the HTTP RFC. probably 1.1, because I think that one was released in like 96, 97 maybe sometime. And I read it sort of all the way through and it was like, It blew my mind to understand all the details of HTTP 1.1. So yeah, and then now I live in Barcelona and I work with WebRTC is like what I do. Streaming media is what I do for work. The HTTP thing and in uReq is like, I guess, a thing on the sides. Something I did. as I approached Rust and started to get my head around Rust back in 2017, 16, 17 somewhere. Yeah.
Glen (Plabayo BV): Okay, very nice. And so that comes with a lot of experience. Now, at what point did you think about creating uReq and why exactly?
Martin Algesten: Yeah, so I came to Rust like many others from Node.js and before that I had been in Java and all sorts of things. ⁓ And I guess coming to Rust from Node.js, sort of, I wasn't around in Node.js, know, initially when you're in Node.js, if you wanted to do an HTTP request, just... pure Node.js. This was before the fetch API. It was really painful because it was this callback based API that you would just pull your hair out. It was so hard to make a single request. So everyone was using like these other NPM packages that would help you to do the HTTP requests in a more kind of ergonomic way. Maybe that was always the intention of the Node.js. authors anyway that you shouldn't really use the sort of raw API for it. ⁓ And towards the end of my kind of Node.js and TypeScript and so on journey, there was a client called Super Agent that was written by a Canadian guy that again pushed the boundary on making things even simpler and even more magical in terms of making HTTP requests. And ⁓ Superagents, I guess, have now been superseded by Fetch API and so on. No one really looks at those anymore. But what it did was it's kind of focused on the user being like, what does a HTTP client user do in the context of Node.js? And then when I went into Rust, that was what I took with me. ⁓ When I started doing requests in Rust, there was the request library, the request with a W and it did what it said, it worked. ⁓ But I always felt that it was like a little bit too hard. I just want to make a request. And I knew from like all the way back from reading the spec back at university and so on that HTTP, well, I mean, There are hard things about it, but it's not like super hard. It shouldn't be this hard. Can we make a more ergonomic user kind of friendly? Not friendly, because well, anyway, I guess the point is that Jurek came in some kind of childish kind of, it should be simpler than this kind of mentality, which I think is maybe looking back at it as a little bit sort of silly. But here we are. And I also wanted it to be sync first or sync only. I didn't particularly care for async. Well, there are cases when you want async, of course. And if you do a massive kind of back end taking many requests and want to process much of IO, then of course you want an async solution. And often you would have an HTTP client sitting in a context like this. But there are also cases where you just want to build some little plumbing service, some little thing that does something. And I just want to sync simple sync library. doesn't need to have a runtime or anything. It just does a request. So if we combine the idea of sync and a simple HTTP client and something where I wanted the API to be very simple. That's the kind of starting point of uReq and I started hacking at that somewhere in 2017. I saw the first commit is and then sort of spinning from there. This was also when I learned Rust. So for me, what was simple is basically what you see in the standard library. So I like when I see, you know, stir. ampersand string there or ampersand like a byte slice ampersand you ate. I like all of those kinds of simple types that comes from standard library where I just, I recognize them instantly. that's, if I, if I want to add a header, ideally it should just like take two strings. doesn't need to be something where just like, you know, trade bounds and this and that, and the other, that was the mentality. Now you wreck today. is a little has gone away a little bit from having super simple types for reasons that we can come back to. So answer the question of the starting point.
Glen (Plabayo BV): Yeah, yeah, yeah, but anyway, I do think it's good to start simple. And given that refactoring in rust is anyway pretty painless, like I think that's the right approach because and especially it, I mean, even though you read the spec, I think it's fair to say it was the first time you really had to test that knowledge by actually implementing it. And so if that's true, then anyway, no way you would have known all the things that you know now. And so starting simple allows you to maybe get deeper into what is this all about? What are the real pain points? What are things that actually need solving and what is actually fine to keep simple? mean, am I correct in those assumptions?
Martin Algesten: Yes. And I think maybe I've shifted also a little here where from uReq 1.0 before that on the sort of pre-versions, ⁓ I didn't care about being super spec compliant because I was more in the mindset of just let's just make it work. And then if there are services or requirements calls that don't work because I do something silly with the spec, then, you know, visit those things and fix those things. But I didn't come from a sort of super compliance kind of mindset. And this was, if you look at the, there was a contrast, at least back then, there were a lot of people that were drawn to Rust. Initially, that was like came from more formal background and they kind of really wanted to nail down. precise protocols in like perfect kind of implementations in libraries. And that was not the mindset I was in when I started the first iteration of that. It was more like, let's just make it work, whatever. I have again, I've changed since then. Now I am a little bit more like, actually let's try and be a little bit more strict about it. But yeah, that was the niche to start and these days, yeah. Along the way, I got some help from people that were more into to spec things. And they sort of helped me out to straighten up some of the worst kind of things I do. There's a guy called Jacob Hoffman-Andrus, who has been amazing in terms of ⁓ kind of shaving the rough edges of those initial uReq versions to make them. much better than what it was initially.
Glen (Plabayo BV): Okay, and maybe as we are talking about the subject, first of all, did these shifts towards a more spec-compliant piece of client, did that come mostly from external pressures or did you... Wanted yourself and what were the reasons and then secondly could you maybe give more two examples that are sale? Okay This is what you like it before and for this and this reason we are now doing this and that's how it's doing it now Can you maybe talk with about it?
Martin Algesten: Yeah, so the reason, as I said, uReq, what it is for, I think it fills a niche of doing, it's mostly going to be used against ⁓ APIs, right? You're going to use it to integrate with other services ⁓ that are having an HTTP API. So it's mostly going to be focusing on JSON is going to be focusing on kind of you know, maybe some streaming kind of setups where you sort of download big files and you can't download everything into memories, need to stream it to disk and this kind of thing. But it's not going to be used as the kind of backend in a browser or something. It's not going to be, that's not what it's for. It has this niche of being something we use in a service. And particularly I would say a sync service, because again, if you have this kind of scalability risk, scale reason that you need to accept massive amounts of I.O. it is probably not the right thing. But for something which is like just something that needs to do a sync call, that's where we're coming from. And ⁓ in terms of what do we need to do in terms of spec compliance and how does that affect those decisions? ⁓ I mean, thinking back, I know one thing that is in the HTTP spec that initially Eurek was not caring about much, which is that ⁓ HTTP ⁓ value, HTTP header value fields. So if you had the headers and if you take the value side of that, there are actually ⁓ byte ranges in there, which are technically allowed by the spec that do not fit in ASCII. I think that's how we know it fits in ASCII, of course, but it's not a control character. It's not. something you just throw into a UTF-8 parser and get something good out of. So that means that if you are to like do a strict API that lets you consume and work with header values that go beyond or that uses those parts of the spec, then you can't just use the simple types that I was talking about from standard library. It's not going to be a string because you can't represent this value as a UTF-8 string in Rust. So that's, for instance, an example where the initial uReq was just like, ne, it's all strings. No one is using crazy values here. ⁓ And these days, I think, yeah, we'll be okay with handling those as well.
Glen (Plabayo BV): Yeah, then again, sometimes I do see those specs and I think like, I mean, like for example, you mentioned headers and yeah, you can, the amount of the kind of values you can put in them is definitely beyond the range that most people use because in all the years I've dealt with HTTP, I have yet to see a header which is a non-ASCII character, like...
Martin Algesten: Yeah.
Glen (Plabayo BV): Like I haven't seen it so I sometimes I wonder like why are we going through all this effort? Like why are we pretending? Like what are we really serving here? And I'm sure there is someone that's gonna mail me and say like yeah it's used for this. like I mean I really have never seen a non-usky like had a name or value. Have you ever seen one?
Martin Algesten: I sure I probably have because ⁓ when I've been testing uReq and this is someone that helped me with this initially and then later I've done it myself to just try and see if it works okay. You can download somewhere. I can't remember where now, but it's like you can download the top one million sites on the internet or something like that. get it's like a a text file with just a bunch of URLs. And then you just write a little client that just tries these and then log what kind of errors you're getting as you're going through this and going through, I mean, I don't know if I run the whole list ever, but going through it for a few hours, you always encounter weird stuff. And I think I found these headers to trip up the early uReq and now We're fine, but it's like, it was definitely... It is definitely out there. If they're out there for legitimate reasons, I can't say.
Glen (Plabayo BV): yeah but then again you could also just choose to ignore them i guess like okay you you might have counted them in parsing but that still doesn't mean that you have to provide them to the users
Martin Algesten: Sure, but also you shouldn't choke on them and throw an error because you couldn't parse it properly, right? So yeah.
Glen (Plabayo BV): No, no, no, no, I would probably either like give it to the user or like ignore it, but yeah, I guess it's up to the user at that point. But yeah, mean, uReq is used quite a lot as far as I know. It's like a couple of hundred thousand downloads per day, which is like quite significant. And even though I am an async proponent myself, I like it, but it's mostly because of the kind of workloads I run.
Martin Algesten: Yeah.
Glen (Plabayo BV): I also know that for most of my career we didn't do async at all and it's not like the world was like any slower and we always talk about scale but I would say for the typical service it's handling such a low scale that who really cares and even if you are stuck on your one core you can probably do like two or three or four cores and again you are fine as well I suppose so sometimes I think we are worrying a bit too much about scale just like maybe we are going too fast to Kubernetes or this or that like I'm sure they are like yeah
Martin Algesten: Yeah, think we reach for the async runtime a bit too quick. I agree with you there. There are definitely cases for async, but not everything is an async-shaped object. And it becomes a bit annoying because if you think that, okay, this doesn't need async, I don't need an async runtime. then you want to use something like say the ⁓ AWS APIs that they have published on, then their default is an async API. So you kind of force into this reality whether you want it or not. ⁓ And I guess I'm still sort of trying to fight the sync fight. well.
Glen (Plabayo BV): Yeah, I mean, of course that's also a bit because Rust has this coloring problem. If it wouldn't have that, then I suppose it might be less of an issue where it wouldn't leak all the way through. Well, now, yeah, you kind of once you have something, I think you somehow have to contain it, because even worse if you sandwich them and you go from a sink to sink to a sink to a sink. I mean, you're...
Martin Algesten: No, I mean, it's definitely possible to work with async stuff in just on one thread. Like if you looked at requests library, for instance, as an alternative to uReq, you can run that in a sort of static API mode. You still have a Tokyo in the bottom, but it's like a Tokyo that is just running, I think, on the local thread and doesn't really blow up into a full kind of sort of work ceiling, whatever.
Glen (Plabayo BV): Hmm.
Martin Algesten: So it's definitely possible to do that. ⁓ So yeah, you can deal with the asyncness of things, but it's like, yeah, I think that there are many cases where it's nice to not reach for that.
Glen (Plabayo BV): And for similar reasons someone could spawn uReq or a couple of those clients on some threads and via channels move into their like ⁓ async environment. So even if someone has an async app already but they still want to use uReq, I mean it's still possible right?
Martin Algesten: Yeah, I mean, there is all the spawn blocking stuff and there's always ways around these things. It's totally possible. But yeah, it's not because the IO ultimately in uReq is sync. It's not like a great fit to put it into an async runtime. You can get away with it in some cases, but maybe shouldn't do that normally.
Glen (Plabayo BV): Yeah. Yeah, and so. Maybe we can talk a bit about Sans IO because we had it, we talked about it last time and the way I usually describe it and then you can correct me and as more of a practitioner you can maybe explain it better. But the way I always explain this, you basically decouple the entire protocol work or business logic from the IO layer. So what you in the end just worry about, let's say with HTTP is that you get some bytes in and at some point you need to give bytes out and how those bytes are then being transferred.
Martin Algesten: See ya.
Glen (Plabayo BV): transported you don't really care about. Is that kind of like how you describe it or what am I missing?
Martin Algesten: That's right. Yeah, exactly. You effectively ⁓ decouple everything, which you would call a socket or a file or anything that is I.O. from the actual library and then make it possible to call the library in a way which doesn't expect sort of to read from the bottom from things like this. you look at Rust standard libraries, you have the read and write trait. Now these traits are great, but they are effectively then locking you into a pattern of not Sans.io but rather having I.O. of embedded into what you're doing. I think that, yeah, mean Sans.io and uReq is also a bit of a journey in the sense that we didn't, uReq didn't start out with Sans.io. I didn't have no such ambitions in 1.0 and 2.0 and We're now on uReq 3, right? And so that Sans IO idea, I've sort of, you know, got into that more and more the last couple of years and uReq 3 was released last year in beginning of the year. And the whole sort of reason that I went from uReq 2 to uReq 3 was because I wanted Sans IO. There were a couple of reasons for uReq 3 over 2. One is the SansIO thing that I wanted to like formalize the backend to make a SansIO implementation. That in itself will allow us to be transport agnostic. I guess we have a sort of plugin way that you can bring your own transport to uReq. Don't have to ⁓ use what's built in. If you want to, I don't know, use another TLS or socket or something, you know. that you wanted to run on top of. And this is being used by other projects. I think there's some P2P kind of ⁓ projects and things like this that are using uReq exactly like that. There you can sort of plug in your own transport. The third thing that was the motivation for uReq 3 was there is an API called HTTP API, and it's just published as the crate called HTTP. And this is the API that you encounter when you use request, but they sort of got their own kind of little version of it. But the HTTP API is effectively like a ⁓ standardized HTTP API that you encounter in many places in the Rust ecosystem. And I wanted to use that as well, because again, if we look at sort of people liking sort of being accustomed to what they've seen before, it's good to be basing the entire thing on that API. So this HTTP API, it only sort of encapsulates like requests, response. It has a certain way it deals with body and how it ⁓ deals with headers and all of these kinds of things. But it's just like data structure. doesn't actually do anything. It's like just giving you the model for how you do that. So those were the reasons we went into UREG 3 and SansIO. ⁓ The way that I started thinking about that was ⁓ I have been toying a bit with embedded ⁓ stuff like microcontrollers and no STD environments where you don't have an allocator and you just have your stack. The challenge initially for myself was can I make an HTTP protocol implementation that is no STD, that effectively all buffers in terms of inputs and outputs comes externally and it has no concept of transport internally. There's a of companion crate to the uReq, which is called uReq Proto. So uReq minus Proto. That's where you have the Sans IO kind of foundation. Now this was initially intended to be no STD, but I didn't reach all the way. So it's still a little bit of work to do completely no STD. think no STD plus an allocator will be totally doable. ⁓ No STD without allocator, I am not so sure because again, it's based on these other crates like the HTTP crate and they have no ambition to make those things without allocators. The whole SansIO, if you sort of want to look at the uReq, if you want to look at it, SansIO implementation that is quite clean and ⁓ has nothing like you can use it as it is, uReq proto effectively shows you how you can do, it has both a client and a server, but obviously uReq only use the client. ⁓ And here, I guess is where I then returned to the idea of being more formal that this time, I wanted to implement HTTP 1.1 protocol in a formally correct way. ⁓ So that's sort of my journey going from the original uReq where everything was just whatever to uReq 3 where it's sense.io and we have this also kind of a better compliance with the RFC. I don't know if I'm, know, who might say whether I'm there, but it's... It's certainly an ambition of uReq Proto that it should be a very correct implementation of HTTP 1.1.
Glen (Plabayo BV): Yeah, very cool. And it makes in a way things a lot simpler as well. Now, I mean, it's a lot of work, of course. So I imagine it's still some while to go, which you also set yourself. Now. What did you then use before 3 I mean, still you were ⁓ working on the Sans IO progress, but were you just underlying on the standard library for making a TCP socket or what were you using before as a basic transport layer?
Martin Algesten: Yeah, in uReq 2, it was just standard library socket and then wrapped in TLS because you can't really ship a, I think, you can't really ship an HTTP client without dealing with TLS. You have to have HTTPS in there. So uReq 2 and uReq 1, had that and they had used the standard socket internally. In uReq 3, then The uReq proto has no concept of those things. The uReq create itself is what marries the uReq proto with the concept of IO. And this is then built as this kind of transport API that is something which is also used. You can plug in your own transport. So that again is using standard sockets and standard whatever. And it also has a choice of TLS backends. And that's also something which is like a bit of a challenge in terms of the Rust kind of ecosystem is going through. Like we haven't quite settled on what we're supposed to do. Russells? No, it's Russells that you were talking about in the previous episode. I guess it's standardized. It's pretty much the one that we all use now. But then who are you going to use for the crypto primitives underneath? Should you use Ring or should you use AWS, LCRS or should you use, you that sort of debate I think is still not settled. ⁓ Someone like myself, I would really like there to be a very high quality Rust, like native TLS crypto primitives ⁓ underpinning that. There is a Rust Crypto project that is aiming towards that, but they're not as hard optimized as some of these other projects. That's because the hard optimizing of the crypto primitives is like hand-rolled assembly that's been around since a long time. AWS and LCRS are also using a fork of some other kind C library that has a hand-rolled assembly for that. So now maybe in the world in the future we will have a proper kind of native all the way, but that's not where we are. But a URIK as a crate then needs to kind of pick some kind of reasonable default for TLS provider. And whatever we pick, it's going to be wrong, right? Because some people will not think that you should use ring or some people think that you shouldn't use AWS, LCRS or, ⁓ or, or, know, whatever we pick, it's wrong. This is also why I want to have that transport is pluggable. If you don't like the TLS provider, then bring your own. ⁓ It's fine. And that's again, yeah, it's kind of a constant shifting reality. think the last Russells now, they are to stop having a default ⁓ crypto provider. So you have to like pick one. And then the question is, you react to the same? Like, should we pick one or should we also like say that this is up to the user? ⁓ I sort of guess I'm still leaning more towards batteries included. I still have that kind of the user who is new to Rust wants something simple, wants simple APIs and it should just work. Like you should just like add the crates, make a request. and it just works. That's sort of the goal.
Glen (Plabayo BV): Yeah, I mean that's fair enough and so I want to also a bit focus on maybe some choice you made and lessons you learned from it and then maybe like what problem you solved with them. For example, we ourselves, we develop a framework called Rama and while we are not really song.io in the traditional sense, we do try to abstract the different layers as a very abstract pieces that you can compose however you want. and you earlier mentioned, okay, you have, for example, in a standard library, the read and the write traits, and that's not something you settled on, is that you, as far as I noticed when I was reading through the uReq protocol, you are more settling, okay, I'm passing around like byte slices, U8 slices, but for example, in the RAMA, especially at those transport layers, be it some proxy protocol or some TLS protocol or some L4 protocol or whatever. We basically keep it all at like, think, read, I think, write, because we do everything in I think. But you could draw like a parallelism, okay, you could also do it with like, like read and write. So can you maybe focus a bit and explain a bit on why you couldn't use those traits and what is the advantage of using those U8 slices?
Martin Algesten: If you think about, say, if you take read and you have the read call, you effectively expect to call read and then something underneath the read should block and fill in that buffer that you provided and then return how many bytes it filled out in the buffer. that's not how I would structure a sense IO API. Instead, if I say that I'm going to read like a bit of a body or something, I want to provide both the input buffer, like the one coming from the socket and the output. So you effectively get these, ⁓ if you look at, yeah, if you look at the body, methods, the body read methods in uReq Proto, you will see how they always take two byte buffers, like the input and the output. And if you, for instance, are doing chunked ⁓ transfer encoding, then that will basically just de-chunk the input and put it into the output. And this is, I guess, how I would say Sans-IO kind of differs in that you your network input and you get back your kind of whatever higher level decoded data that you want instead of kind of making everything generic over a read trait and then effectively have like a blocking socket at the bottom of that whole stack. That means In practice, this uReq proto could be used in an async world as well. Because the I.O. is completely decoupled, we sidestep the problem of how that works. And here we have, if you think about then back to the embedded problem, especially if you're talking about very small microcontrollers that don't have much going on, You can't sort of, and you have one single thread, you have nothing else, you can't spawn more. You kind of need to have this where you get an input buffer and you need to process this into an output buffer. You can't have a locking call like read, which basically stops somehow because then what are you going to do with the, what is the process for the going to do while you do that? If you build a little microcontroller that has like say a screen, it has some inputs, maybe some knobs that you're going to do and some buttons, some LEDs, then you can't lock your loop. It needs to keep running. It needs to keep running all the time. And the read and write trait are fundamentally blocking. can't, once you start at the read, well, I mean, there is a little hack around this and that is that there's an IO error that is called WouldBlock I think. So the error kind is WoodBlock And this could be used as a kind of escape hatch that you call read. And then if the read definitely can't be done right now, you throw WoodBlock and then the caller needs to be aware of that if that particular error kind happens, then we are in a situation where we should just continue. shouldn't abort because it wasn't technically an error. It was just meaning it's not ready right now. ⁓ semantics of carrying that in an error is like it's obviously doable it happens it works but it feels like a bit of an escape hatch and not exactly the way I would like to structure my code for that. ⁓
Glen (Plabayo BV): Okay, but let's think of a practical example. And I could do HTTP, but I feel it's a bit more complicated. So I noticed you also support, for example, SOCKS5 proxies. So the way it happens is, you have your transport layer open. So you let your TCP handshake, and then you do the handshake on SOCKS5. And so you initially send the header, you expect the header back. In that header, you notify, okay. And those are just a couple of bytes. I imagine those are already available for the server and they identify which, ⁓ I believe they call it methods they want to use for authentication. And in case for example, they use username password, then that means that the client has to send another header for that part and then the server has to respond, I accept, et cetera. So you kind of have these dances and then I wonder, There will be no way, especially as these protocols carry larger and larger payloads, that you will always have all the bytes available in your U8 slice, right? It's not like you're gonna magically have them in that input. So how do you deal with that? Like how do you kind of like go from like maybe a couple of nested calls all the way to, okay, I need to go back because the transport layer does need to give me things if I want to continue. Like how do you do that kind of dance a bit there?
Martin Algesten: Yeah. No. Maybe like we can think about the streaming body again in GeoRig Pro 2, where we have the input buffer and the output buffer. And we say we have it transferred encoding chunked. So we need to de-chunk the input to provide it to the output. So that means that when you are reading the input, will be in chunked encoding, I don't know if you're familiar with it, but basically...
Glen (Plabayo BV): Hmm. Hmm. Mm-hmm.
Martin Algesten: It tells you like, says, okay, now comes a chunk and it's going to be this many bytes long. And then you send those bytes and then comes another one that says, okay, now comes a new chunk. It's going to be these many bytes long. So you get that kind of thing. Now that can be modeled as a state machine where we need to read the amount and then we say, okay, now we're going to expect that amount. And then we need to get a little sort of backslash and or whatever to end that. And then we're going to get another.
Glen (Plabayo BV): Yes. Mm-hmm.
Martin Algesten: can be modeled as a state enum in Rust. And what you do is that you basically don't read more than you can consume. So say if you can't get the entire input number, that is the number of bytes, for instance, because you get the number of bytes and then I think it's a backslash r backslash n. think it's like Windows
Glen (Plabayo BV): Mm-hmm. Yes, something like that, yes. Yeah,
Martin Algesten: Yeah, it basically has number and then it has like a line break. If you don't find that line break, then you can't really read the number because there might be more, more digits coming. Right. So that means you need to remain in the same state where you're saying still expecting more. And you kind of pass back to the caller and saying, nah, this buffer doesn't have enough data yet for me to be able to consume anything from it. So you basically say I read zero amount from this input buffer. And then.
Glen (Plabayo BV): Mm-hmm.
Martin Algesten: That can go away. That means whatever it's calling, can go away. Okay. I need to read more from the socket then I guess. So you pick some more bytes of the socket and you extend the buffer and now you provide the buffer with a bit more on it. And then it's like, ⁓ okay. Now I can actually make some progress and parse this little bit and then tell that, okay, I consume this many bytes off that buffer. ⁓ and so on. So you do that kind of dance where you have a very tight kind of state machine and this kind of then translates, I guess, into bigger and bigger scale. You have a tight state machine that is sort of modeled in uReq Proto using type state variables and ⁓ enums. And you make sure that you only make progress when you can ⁓ from the input that you are given. That makes sense.
Glen (Plabayo BV): Okay. And does that mean that every protocol has its own state machine and also its own API or do you have some kind of standardized invariance that you use throughout your entire, let's say stack, like how that works?
Martin Algesten: say that... It sort of depends. I think we need to sort of think about it in different, different sort of layers here. Like parsing chunked is like one thing which has to do with body. But when you're doing that, then say your request ⁓ state is in a place where we are expecting body or response state rather. If you, if you think about it, it's like, It's almost like you go, you're going through a state machine from a request to response to start like this. Do you have a request and you fill in some headers or whatever in your request and you probably have a body as well. And then you start writing the HTTP request and the headers to this out, this buffer, because we're only dealing with buffers. You start writing that to the buffer. And here you have like. little loop that can happen as well. Maybe the buffer can't take the entire thing in one go. Sometimes you actually have situations where people have many kilobytes headers because they just have so much stuff in there and cookies and whatever that you people code up in this. Those headers means that ⁓ you can't necessarily expect the output buffer to take all of it at the time. So you need to start like pushing as much as you can. And I think the smallest bit in uReq proto is you have to do one header. can't, I can't do half a header. I have to do one header at a time, at least to make progress. I think that's how I did it. I can't remember it. And that means you keep doing that until you're done with that. And then you can think that, okay, once we've written the entire header, it's effectively like the entire request moves into another state. It's like, okay. Now we're moving into say expecting a response. We're expecting the server to give us something back. So those states on that large level are modeled like that so that there's like no confusion when we're moving from one to the next. ⁓ And here comes sort of some of the finer details on how you need to model an HTTP protocol. There is like one nice little detail of HTTP 1.1 which is called Expect 100. I don't know if you've encountered this. So Expect 100 is like, still have a little header that says Expect 100 continue, which is a header that asks the server to give a kind of early response to say whether or not it's going to accept what you're going to do. So say this is used when you say posting a large body. So you're going to post many megabytes of data. Then if you just post it and start pushing the data, the server has no way of saying like, hello, stop, I don't want this data. You can't do this. It's too much. And that's what the expect 100 continue mechanism is supposed to solve. That basically you say, expect 100 continue. The server gets like a little brief chance to say, uh, that's okay. You can, you can go ahead and send your body or it can say, no, no, no, that's, that's, can't accept that. Here's some error code that shows you why, why you can't do that.
Glen (Plabayo BV): you
Martin Algesten: That means that you get this kind of branch there where if you sent an Expect 100 header, I can't just start pushing my request body. I need to actually wait first and see if they get the Expect 100. And then that in itself has the problem that not all servers understand the 100 continue thing. And that means that you need to just start pushing the body to be compliant because it's like, you kind of wait. a second or two. I can't remember the value but it's taken from curl, whatever defaults it there. All of these things also is encoded not as... these are not encoded as ⁓ timers inside uReq Proto. All concepts of timers and buffers etc are external to uReq Proto. So it does know that it needs to wait but it's up to the caller to decide how long are you going to wait for before you move on and force us into a state where you're the body.
Glen (Plabayo BV): Yeah, yeah. So the way I'm now currently trying to visualize it in my head is you have something that is basically just a dumb reader and writer or reader or writer and it just it has some kind of function it can call to feed those bytes to and essentially I suppose the function returns if it read everything or not and if I suppose if it read everything then you just try to give it more or like like I'm trying to figure out how those how that like feeder let's say communicates with the thing that parses that's kind like what I'm trying to figure out
Martin Algesten: Okay, maybe we should just bring up so I have it fresh in front of me because talking in the abstract is a bit hard for me about exactly how this looks. But say if we are ⁓ What we're given when we start, I call it a ⁓ call in uReq Proto, we take a request. So this is the request from the HTTP API. And as I said, the request encapsulates all the headers and all of these kinds of things from the HTTP API. And basically then I say, okay, now this call, this request needs to be serialized into an output buffer bit by bit. So you get this kind of loop where you're saying, okay, ⁓ call.write and here's my output buffer. And then we say how many bytes were used of that output and whether we were finished with this kind of phase or this kind of initial bit that we're doing. Does that sort of explain how you're thinking that you have this call wrapper that has the request object? And it knows, it has like a little internal cursor knowing how many headers it's sort of shuffled out already. And you give it a buffer where you say write, write, write, write, write to this buffer. And it gradually writes the entire header out. And when it's done, there is this thing you can call every loop that says like can proceed. And if call can proceed is true, that means that the entire header has been written to the buffer of these buffers as they were. And you can move on to the next phase. And if you call call.proceed, the entire thing does this state transition where it's now in a ⁓ weight 100 situation or send body, depending a bit on what headers you had. Does that sort of explain? There is, ⁓ I guess we can put a link in the notes for the episode as well. There is a quite good like, example that shows exactly how this works. I'll send you in chat now. That sort of explains exactly how you move these buffers back and forth to sort of do this little dance. And it illustrates quite neatly, I think, how the buffer and the I.O. is completely separate from the idea of the call. And the call is this thing that basically takes inputs and outputs buffers to do the entire request without IO.
Glen (Plabayo BV): yeah I think it starts to come together but but of course like in your case Yeah, I guess it always kind of works because there are places where I definitely use something like that, where I'm just dealing with, okay, I take some kind of byte slice and I parse something from it. Let's say I'm parsing a client, hello from TLS, or I'm parsing some headers from some kind of primitive protocol and there it works. But I've yet to do that for an entire system. But I guess it could work just a different way of thinking about the problem. And I think in your case it makes sense, maybe it always makes sense. I just wonder how easy that is to compose when you get bigger and bigger systems where like you layers on top of layers on top of layers on top of layers. But I guess it could always work I suppose.
Martin Algesten: Yeah, I don't know. mean, how far we would take this. I guess if we were to build something on top of HTTP, whether we would want to continue the Sans I O thing or not, I haven't really thought about it. As I said, for me, it's like the idea here. What if I have a microcontroller and I basically want to do HTTP requests from my microcontroller and this microcontroller does have some kind of network stack, you know, but it's would be very sort of just IO based, then I should be able to use this straight up with that more or less. But then I don't know if you're going to build like, say if you're going to do some bigger application on top of this, I don't know how far you would push the SansIO pattern because eventually I think you will have a system which is like have
Glen (Plabayo BV): Yeah, yeah, yeah.
Martin Algesten: allocators and all sorts of things and it's like not you don't need to be on this level. ⁓ So maybe it's more for low level. I guess if you compare to WebRTC and Strum that we did in the previous episode, I would say Strum is more complicated than uReq Proto in that Strum takes the SansIO and has more of an application layer built on top of it with lots of timers and lots of things going on but it's all externally driven by handle input, pull output. Now strom had ⁓ no ambition to be no STD but then as it happens just last week someone has said that they basically hacked it so it's working on no STD with allocator so it's like okay it's pretty cool. ⁓ That's taken much further, like say when you have timers, but you say that time is also an input and an output to strum. In uReq Proto, there is no timers internally in this. That would be an external concern. But in strum, because of the way WebRTC works, you need to have timers. need to have, you know, have resends of all sorts of things. You have like ⁓ error correction. You have all sorts of things going on that needs to sort of know.
Glen (Plabayo BV): Mm.
Martin Algesten: But the time is driven externally, ⁓ because that's sort of what we say with Sans IO right? It's like you provide these things.
Glen (Plabayo BV): Yeah. Yeah, And anyway for you, Rick, I think you can assume that your entire parsing and this and that is anyway fairly instant. And the only concerning spot where you want to deal with time is anyway where you are actually reading and writing from some kind of socket or actual IOL, right? I mean, that's going to be your blocking part.
Martin Algesten: Yeah, that's right. exactly. So if you were to timeout reading a request, for instance, that's something that, well, it's basically then setting a timeout on your read call to the socket, for instance, if that's appropriate way of doing the timeout. ⁓ And this is something which is maybe is a part of uReq, not proto, but then if we are on the uReq client, it does all of this in terms of timers. trying to to mirror quite closely, you know, it doesn't have as many knobs as say curl or something, but it tries to give you the basic knobs for giving timeouts and giving these kinds of things. And they should be reasonable out of the box. There are some parts of that, which is just like impossible, say DNS lookups. There is no uniform way of doing DNS lookups with a timeout. So you... You basically, if you want to have timeouts on that, you need to have spawn a thread and cancel the thread if you don't want it. ⁓
Glen (Plabayo BV): Yeah, well, maybe that's actually kind of like a good segue because it allows me to maybe try to find the maybe the fringes of that approach. So, for example, like I discussed before with you, I noticed that uReq supports proxies. And so you have the HTTP proxy. You also have the SOX 5 proxy. And if we talk about the SOX 5 proxy, then... you have like SOCKS5 and SOCKS5H and in SOCKS5H you let the proxy server resolve the domain of the proxy but normally in SOCKS5 the regular one it is the client that will resolve the domain but that means you were were kind of in this simple world parsing flow and now you kind of have like a little side branch like you kind of need like a new loop within the loop because now you need to certainly resolve DNS while you are still in this parsing flow like it's it's
Martin Algesten: Yeah.
Glen (Plabayo BV): like a branch but it comes back to the original like parsing loop like how do you deal with that kind of thing
Martin Algesten: Yeah, I mean, uReq itself is not Sans IO, it is with IO. ⁓ So the problem doesn't really arise in the sense that I'm allowed to have timers and I'm allowed to do all these things in uReq. ⁓ It's trying to model like the SOX 5, SOX 5H ⁓ in a kind of uniform way, you know, in the sense there is like, should I do this Diena Sluak look up or should I just send the domain name in the call itself and rely on the server. ⁓ Those things are kind of happening not really as part of HTTP at all. Those are happening around. So it's nothing to do with the input and output of the protocol, right? ⁓ So that's just like, I guess scaffolding and it's sitting in these
Glen (Plabayo BV): No, Yeah, before, right? No, no, no, it's correct.
Martin Algesten: If you look at the kind of pluggable transport stuff that we have in uReq, there is also there a pluggable resolver. you can, if you have some special needs in terms of DNS resolver, because this has also came up in the past that people need something unique in terms of how they want to resolve a name to an IP address, then you can. provide the resolver implementation yourself. And the default one is just using lookup with the standard library. And that's also in itself a bit of a problem because the standard library lookup, the standard Rust library doesn't have an explicit kind of lookup call, right? You just sort of have it, that's something that I wish would come, but yeah. There is a way of giving it a domain name and getting the IP address out, but it's sort of implicit and you can't configure it in any way. It just is what it is.
Glen (Plabayo BV): Is it by just like is it just by maybe binding a socket or something or like Because I know like both at least in Tokyo I know that's what you do you have something that you just open a socket and then you get like Yeah, even if you give a domain it will just resolve it somehow. You don't care about it
Martin Algesten: I need to refresh my memory here That's right. ⁓ Let's have a quick peek at how it's done. ⁓ So in the default resolver, basically we have... Yeah, it doesn't matter really. People can look this up by themselves. It's sort of a waste of time here. But when you have an address, there is something called toSocketAddress. So you basically have this call which takes... ⁓ It takes a name, a host name, and then you have a call to get IP addresses for that, which isn't like, it isn't doesn't, it isn't like resolve. I can't really control it. It's just like how I convert, I convert a host name to an IP address. think that's how it works, right? ⁓
Glen (Plabayo BV): Yeah, yeah, I didn't know that this function was even public I I've never used it myself
Martin Algesten: Yeah, it's two socket address and it's basically something... It does not assess on the tin, but ideally I would like more control over the DNS resolution and have like more of an API for DNS resolution than this implicit conversion, I guess, is what I'm getting at.
Glen (Plabayo BV): Yeah, yeah, because I'm so entrenched in the Tokyo ecosystem and they also have like a two socket address trait, but there it's private and they note, okay, if you do want to just ⁓ do a DNS lookup for an address, then they have an explicit function called lookuphost, which I imagine is just using the private function. And maybe that's something you would be more happier with because you don't really want to care with. this kind of traits but it's kind of the same I suppose.
Martin Algesten: Yeah, I can't remember the ins and outs of this exactly, but I think there was... There was things I wanted to do with the DNS lookup that I can't. And especially, I mean, this is the thing is like, I think there is a timeout in, I mean, Rust maybe wouldn't model this anyway, but I think there is a timeout on BSD based systems maybe that you can say that you don't want the DNS result on if it's going to take more than, you know, five seconds or 10 seconds or whatever.
Glen (Plabayo BV): Hmm. Yeah, you should be able to have quite short timings.
Martin Algesten: And this is something that we need to model with threads. If I want to time out the DNS lookup in uReq, I need to basically have a thread that spawns and cancel it. And this is also, I've been sort of very conservative with this in uReq. I don't want uReq to spawn threads. ⁓ And this is, I keep having situations where it's like, would be, that's the only solution. There is a protocol, I don't know if you're... probably where it's called happy eyeballs. You know where you...
Glen (Plabayo BV): mm-hmm yeah I mean you're happy why well one too and and now you even have one for quick and and HTTP to and stuff
Martin Algesten: So you basically try to connect IPv6 and IPv4 at the same time ⁓ or even multiply IPv4 or IPv6. And basically whatever connects first, you take that one. And that's a great idea ⁓ because sometimes even though your DNS resolver will give you an IPv6 address and even though you seem to have IPv6 on your local host, it still doesn't work. ⁓ And this is something that happens to uReq users as well, that if we just pick, if we try whatever comes first, IPv6 or IPv4, and then sometimes it works because it sort of happened to pick, if it was v4 that happened to work. Anyway, happy eyeballs would solve that, but then obviously happy eyeballs in a sync, especially environment, means launching threads. So haven't gone there.
Glen (Plabayo BV): Hmm.
Martin Algesten: yet, but it's something that basically I, it's on my to-do list. We need to do it. So as a kind of configured extra, not the default, I think, because by default, I don't want to launch threads. I don't want Eurek to kind of get that life of its own where it's just doing all those things as well. I think when you're programming sync, at least when I'm programming sync, I'm thinking I have this thing on this thread and I control if I want another thread doing things. That's my... concern, not something the library should just like spawn off a bunch. Which obviously is a problem that doesn't happen at all in the async world. It's just like, whatever, you have just another task and off you go.
Glen (Plabayo BV): Yeah, I mean, I must say my life is quite easy there and yeah, like I said, I do try to implement these simple parts where I can and then use them. But I do really like the freedom where I can just put all these different components together like Lego blocks, especially when I'm dealing with complex. maybe proxy setups where I have many different protocols layered on top of each other. It's quite freeing to not have to worry too much about how they work together because they each have a very clear boundary, which is just this kind of async read write like contracts. And it does make their my life simpler. at the same time, yeah, you do have a lot of implicit assumptions there and you do lock in a lot of things. And so I suppose my
Martin Algesten: Hmm.
Glen (Plabayo BV): One of my biggest takeaways from talking to you is having this really IO approach, which you can do for your protobook, because it's really just dealing about HTTP and especially just, I guess, HTTP serialization and deserialization, where you really just want to send requests on server client and also the response server client. That part, yeah, it works fantastically and it means you never have to worry about... timings or blocking or weird error codes because in the end they just I have a buffer that I read from or write to and I'm just parsing and it's quite a lovely world where you can really pretend life is simple and it's nice.
Martin Algesten: So easy. Yeah, I mean, this may be something we should have started off when we talked about all of this HTTP 1.1. uReq is only HTTP 1.1. ⁓ It has no ambition of doing 2.0 or HTTP 3.0 ⁓ because basically it thinks about simplicity. I'm sort of... I know there are differing views about this in terms of...
Glen (Plabayo BV): So easy. Yeah, I understand.
Martin Algesten: Some saying that we should like retire HTTP 1.1 because ⁓ there are these kinds of misbehaving proxies and stuff, then there's a risk of sneaking in with requests. What's it called? called... Yeah, smuggling. That's request. Request smuggling. Basically you can end up in incorrect states where you have another request lying in the TCP socket because of reusing sockets.
Glen (Plabayo BV): Payload smuggling or request smuggling also. Yeah, yeah. But that works with any protocol, right? That's basically just about once you have three or four spots that deal with the same input or output and they interpret it in a different way that can even be for something like parsing an email, like if you're a security system and it sees your email as different as the authentication system or whatever, then you have the same issue, right?
Martin Algesten: Yeah, I think personally this, yeah, it is definitely a risk with HTTP 1.1, but I think that the simplicity of the protocol is also its beauty. ⁓ we like, you know, with HTTP 1.1, it's back in the nineties when I started playing with it, you could just telnet into a server and you can do a complete HTTP request manually. It's so simple to understand what's going on.
Glen (Plabayo BV): Hmm.
Martin Algesten: And if you do something like HTTP 2 or 3, you can't do that. I mean, with 3, then you even have UDP going. it's like, yeah, you need, it's very tightly tied to quick. With HTTP 2, it's like, it's called HPEG or something. So, I mean, it's still like compression and stuff built in. And it's like, yeah, you can't, you can't just discover the protocol. You need to, but I guess most of things are TLS these days anyway. I think that there is a, there's definitely a room. for a simple protocol HTTP 1.1 for some time to come. I don't see everyone moving to...
Glen (Plabayo BV): do you? Well, I think forever because... Yeah, I think it's forever because like some people think one replaces the other, but they all have their use cases. For example, HTTP one or 1.1 especially is still the most performant one if you are streaming like a large file because there's no overhead almost like pretty much your entire like data stream pretty much your entire like transport is just your file while with HTTP two there's a lot of things happening in between and like it's if you if you compare the performance of those that then HTTP 1 is definitely faster, then HTTP 3 is nice if you're dealing with very mobile connection and those things, but if you're within data centers then HTTP 2 is still definitely nicer than HTTP 3. And so I think they all have their use cases and I agree the simplicity of being able to tell that to a server is nice even though you might not do that still the tooling around it is quite mature. And I also don't really see a reason to... I don't think it's ever gonna retire. Sadly we couldn't stop the TLS ⁓ craze, but at least HTTP1 is there to stay.
Martin Algesten: I think so. think that there is a case for it, especially in these kind of API communication, service to service communication, ⁓ where doing something like HTTP 3 would just be like, there's really no need to go to that level of complexity to make a request.
Glen (Plabayo BV): you Yeah, And of course you talked about, I don't want to deal with treads, but I mean, once you go in HTTP 2, then I guess you also implicitly want to have to deal with treads, right? Because you are, I suppose there is a way to do it without treads, but it kind of defeats the entire purpose of HTTP 2 almost.
Martin Algesten: Yeah, I mean, yes, that's right. Like the whole streams idea with HTTP 2. think it will be, I have looked at that and thought toyed with the idea of implementing it with Sync. But I think in practice it's like, God, it's like all these streams working next to each other and how you would need to structure that. mean, maybe it's a brilliant Sans IO project, but ⁓ I wouldn't want to do that.
Glen (Plabayo BV): I mean I guess you could still... the parsing of it and the encoding of it I'm sure that could be done and then... yeah... well...
Martin Algesten: Yeah, yeah, sure. No, sure. But also this has been so comprehensively solved by in Rust by that library called h2, which underpins, know, request and the rest of them. It's like, if you look at that code, it's beautiful. ⁓ It's a solved problem. ⁓ And I don't see.
Glen (Plabayo BV): Well, I wouldn't always call it beautiful though. I think it definitely comes from a different time of rust. I think if you would write it today, you could definitely write it more elegantly and easier to understand.
Martin Algesten: No? Okay, you think like that. Okay. I see what you mean. Okay, yeah. I guess I haven't looked at it for a few years, couple of years.
Glen (Plabayo BV): Now a bit zooming into the future like you have now uReq proto is not finished like is uReq itself already using your proto it is finished okay then i'm wrong so good that you correct me
Martin Algesten: What do mean it's not finished? It's finished, isn't it? I think so. I don't know what else you want from it.
Glen (Plabayo BV): It should fly to Mars.
Martin Algesten: Yeah, no, I think it does pretty much everything it should do in terms of there's... Let's see, there is one error code that I've been thinking about. There's the reader... Well, whatever. I think it's finished. I don't think there is much more to do in there. There is spec compliance if there are problems that we find that this is just not the way it should work. or, you know, there can definitely be optimizations, but I think that it should be fairly complete in terms of what the HTTP 1.1 spec is. And there also is a server part in there, which I haven't used yet in a project of my own. have a prototype project that I've started to make a ⁓ sync HTTP server as well, based on the same kind of underpinnings. But this is...
Glen (Plabayo BV): Very nice.
Martin Algesten: Yeah, it's just a project that's been going for quite some time now, but I haven't sort of pushed it to the point where it's going to be anything. ⁓
Glen (Plabayo BV): okay so that means uReq is already using uReq proto
Martin Algesten: Yeah, uReq is using uReq Proto. That's how it works.
Glen (Plabayo BV): Okay, very cool. Githa explains why uReq became smaller than I realized. Because last time I looked at it, seems like, seems it was more stubborn there and now it looks ridiculously small. Not ridiculous, but like definitely smaller than what I would expect.
Martin Algesten: haha Yeah, look at if, if you look at the uReq source code, there is a file called run.rs and in run.rs you see exactly how uReq proto is being used to execute the whole request from top to bottom in like one flow. And it is quite readable and understandable what's going on. So it makes a kind of separation of the two quite, quite clean.
Glen (Plabayo BV): Yeah, yeah. I mean, that's what good abstraction is, right? So well done. Okay. So then maybe a question around like, okay, given that you now have uReq Proto and it kind of like handles the HTTP port and so somebody could also just use uReq Proto, not even having to worry about uReq. Do you also have ambitions to then like decouple that also for other protocols? Let's say for example, Socks 5. Is there ever like an ambition to also do that?
Martin Algesten: Hey. I haven't sort of really pushed into socks and proxies. This is mostly, I guess, because I have yet to be in a professional environment where someone forces me to use a proxy for my... I don't know if it's luck or whatever that is. So this is like a little bit of a weak spot for me because mostly when people need things to happen in uReq in terms of proxying...
Glen (Plabayo BV): You
Martin Algesten: I'm like, thank you, please help. I don't know exactly how this should work. And there are some problems right now in terms of environment variables and picking up config that needs to be ironed out, but it's... I guess I always approach these things in terms of what is bothering me personally. What do I work with? And uReq at this point is like for me, I will keep maintaining it. I keep it up to date and I keep fixing bugs and whatever. It's not my main priority because I have my kind of media and streaming and all this kind of stuff that I do in str0m So sort of on other challenges, but it's definitely something that I plan to keep just going. for, you know, as long as ⁓ it doesn't take much admin at this point ⁓ because it's, guess, people are not encountering that many bugs with it, at least, I guess, that's what it means. Or people are shutting up about it and just being angry in silence.
Glen (Plabayo BV): So given that you focus on HTTP 1.1 and now you also are finished with the entire refactor and decoupling the protocol, you have uReq Proto, you're using it now. It's not like there are new specs coming in from HTTP 1.1 anyway. ⁓ What is the future for uReq? Like it's kind of like now we're just a finished project and like people can just use it and okay, when some small problem come in, we fix it, we try to find solutions, that probably more like the details because the work is kind of done. Is that how you see the project or you still see big pieces that needs to be tackled?
Martin Algesten: I think it's done. ⁓ uReq 3 also got rid of some code that was sort of in base in uReq 3, in 3 in uReq 2 And we rewrite in uReq 3, we sort of rejected that. This is like, say there is a whole system of doing middleware. So you can have middleware that say should do extra authentication or should do whatever. Those things are things that I would like to see keep outside of Tree and people can put it in separate crates. And the same goes for transport implementations. If there are big popular transports that ⁓ then we don't have to have Mint Tree. They can be published as separate crates and just instruct how you configure it to use it. So I think that it's sort of finished in that respect. ⁓ Then, I mean, maybe there will be new ways of doing Sans IO or new ways to do rust or whatever that would sort of make me feel again like, I should really revisit that and improve the code and make it sort of nice. mean, that was, that's always a thing when you start, I start having this itch, right? Like in uReq 2, it was just itching more and more and more and more until it was like, ⁓ I have to rewrite it. So then uReq 3 comes. I had, yeah, some other mistakes I did before was like exporting, crates and APIs from other crates like the cookie, the cookie store and cookie jar, whatever those APIs in uReq two, those was like ex extern exposed dependencies, which meant that if they broke their did a major sort of, they were still on pre pre versions in terms of Semver. If they broke something, then that would break my API. And technically I need to then provide a Semver major release for that, which I didn't do. But that was like a lesson I learned is like, okay, don't expose all of these kinds of sub APIs unless they are on stable versions. Cause I want uReq to kind of settle down. I don't want to have an API that just sort of breaks all of a sudden and whatever. ⁓ The uReq 3, if you also look at the API, the transport stuff is under a ⁓ path right now, which says unversioned. This is a little bit that I would like to have a bit more like feedback on that I've reached a good state with those ⁓ and see that someone sort of implements a bit of sort of crates on top of that or what you know just see that it's that it is doing what it should before I move it into versioned ⁓ like that it actually follows Semver for just the transport the pluggable transport it's like that part is I see as not stable Semver wise in uReq If you use uReq as an HTTP client, it's stable. It's frozen API. It's not going to change. But if you have written a transport, maybe that could break. But I see that those that's like in the future, that's making the one thing that would be like fixed. It's like, okay, let's call that frozen API as well. And just say that this is not going to change it either as well. ⁓
Glen (Plabayo BV): So you're basically requesting any listener which might be using uReq, like please try to implement these bounds and give me feedback.
Martin Algesten: Yeah, mean, if you have a need to do your own transport or you already are using your own transport because it's happening and I don't know about it, if there are any kind of frictions or something with that, or you just want to tell me that it works, whatever, I'm happy to receive any feedback on that because I think that sort of will guide me on when I will finally say that that's frozen API. ⁓ The other problem is the constant minimum supported Rust version. ⁓ I try to be extremely conservative on that because I think that Rust ecosystem generally is updating a bit too much. I guess what I'm trying to say is there are a lot of central crates that seem to bump MSRV in a patch version or something. And that's as in... consequences for everyone relying on that crate and that's something which I tried to I try to be very conservative and that's sort of biting me a little bit, but I don't see I think for the consumers of uReq, I got so much feedback about this that it's being appreciated for being slow update and slow conservative of those things. I do not bump MSRV unless I absolutely have to and right now I'm in a situation where I probably have to because The time crate have a RustSEC advisory that ⁓ can't be solved on the MSRV version that I'm on. So I'm sort of facing one of those situations where the only way I could not have that RustSEC advisory on me is by bumping as well. And this is going to be much better on Rust 185. We have an resolver that is MSRV aware. So that will solve a lot, but I'm still not there on Rust. But the next bump I do, will get... in D-REC, get there.
Glen (Plabayo BV): Very nice. And so I believe we discussed everything I want to discuss. Is there something else you would like to plug in or mention that you feel is worth mentioning here?
Martin Algesten: ⁓ no, think we covered everything.
Glen (Plabayo BV): Very cool. In that case, I'm very grateful for your time today. I mean, I learned a lot. We got a glimpse from the maker of uReq into the internals, into the transition, what the future brings. I mean, I learned a lot today. I think all listeners as well. So thank you very much for that, Martin, and for all the work you've been doing on all your open source crates
Martin Algesten: Thank you. Bye bye.
Elizabeth (Plabayo): Netstack.fm is brought to you by Plabayo building secure, open, and resilient infrastructure with Rust protocols, and purpose. This show is also made possible by Rama, the open source networking framework. Plabayo offers service contracts and welcome sponsorships to keep building and supporting its ecosystem. The theme music of this podcast was composed by DJ Mailbox. If you enjoyed this episode, don't forget to subscribe on your favorite podcast platform and leave a five-star review. It really helps others discover the show. Thanks for tuning in. We'll see you next time for the next handshake.