Give each stream defined in variables a unique yield name. The version of Flux packaged with InfluxDB 1.7.4 is a little older, but I don’t believe this is required any more in newer versions of Flux (which should roll out with the next InfluxDB release.
I also notice the mapping syntax in your join isn’t quite right. This query works, although the data needs to be transformed a bit more to get a useful visualization.
duration = from(bucket: "_internal/monitor")
|> range(start:-12h)
|> filter(fn: (r) => r._measurement == "httpd" and (r._field == "writeReqDurationNs"))
|> window(every:1h)
|> difference()
|> yield(name: "duration")
requests = from(bucket: "_internal/monitor")
|> range(start:-12h)
|> filter(fn: (r) => r._measurement == "httpd" and (r._field == "writeReq"))
|> window(every:1h)
|> difference()
|> yield(name: "requests")
join(tables: {duration:duration, requests:requests}, on: ["_time"])
|> map(fn: (r) => ({ _value: r._value_duration / r._value_requests }))
|> yield()